Merge lp:~christopher-hunt08/maus/maus_analysis_devel into lp:maus/merge

Proposed by Christopher Hunt
Status: Merged
Merged at revision: 740
Proposed branch: lp:~christopher-hunt08/maus/maus_analysis_devel
Merge into: lp:maus/merge
Diff against target: 917 lines (+750/-20)
7 files modified
bin/scifi/tracker_resolution_plots.py (+45/-0)
src/common_py/analysis/__init__.py (+1/-0)
src/common_py/analysis/analysis_track.py (+141/-0)
src/common_py/analysis/hit_types.py (+217/-5)
src/common_py/analysis/inspectors.py (+91/-8)
src/common_py/analysis/systematic_error_estimation.py (+247/-0)
src/common_py/framework/single_thread.py (+8/-7)
To merge this branch: bzr merge lp:~christopher-hunt08/maus/maus_analysis_devel
Reviewer Review Type Date Requested Status
MAUS Maintainers Pending
Review via email: mp+345284@code.launchpad.net

Description of the change

Some tweaks and additions to the analysis code in src/common_py, plus a bug fix in single_thread.py where using the data object wasn't being correctly deleted at the end of the pipeline.

To post a comment you must log in.
Revision history for this message
Paolo Franchini (p-franchini) wrote :

Thanks Chris,

I will do it later today,

cheers,

Paolo

On 09/05/18 10:32, Christopher Hunt wrote:
> Christopher Hunt has proposed merging lp:~christopher-hunt08/maus/maus_analysis_devel into lp:maus/merge.
>
> Requested reviews:
> MAUS Maintainers (maus-maintainers)
>
> For more details, see:
> https://code.launchpad.net/~christopher-hunt08/maus/maus_analysis_devel/+merge/345284
>
> Some tweaks and additions to the analysis code in src/common_py, plus a bug fix in single_thread.py where using the data object wasn't being correctly deleted at the end of the pipeline.
>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bin/scifi/tracker_resolution_plots.py'
--- bin/scifi/tracker_resolution_plots.py 2017-12-12 10:11:06 +0000
+++ bin/scifi/tracker_resolution_plots.py 2018-05-09 09:31:35 +0000
@@ -318,6 +318,11 @@
318 tracker+'_p_residual_p', "p Residuals in p", \318 tracker+'_p_residual_p', "p Residuals in p", \
319 PZ_BIN, PZ_MIN, PZ_MAX, 500, -50.0, 50.0 )319 PZ_BIN, PZ_MIN, PZ_MAX, 500, -50.0, 50.0 )
320320
321 tracker_dict['dpp_residual_p'] = ROOT.TH2F( \
322 tracker+'_dpp_residual_p', "dp/p Residuals in p", \
323 PZ_BIN, PZ_MIN, PZ_MAX, 500, -1.0, 1.0 )
324
325
321326
322 tracker_dict['x_residual_pt'] = ROOT.TH2F( \327 tracker_dict['x_residual_pt'] = ROOT.TH2F( \
323 tracker+'_x_residual_pt', "X Residuals in p_{t}", \328 tracker+'_x_residual_pt', "X Residuals in p_{t}", \
@@ -1016,6 +1021,7 @@
1016 tracker_plots['pt_residual_p'].Fill( P_mc, Pt_res )1021 tracker_plots['pt_residual_p'].Fill( P_mc, Pt_res )
1017 tracker_plots['pz_residual_p'].Fill( P_mc, res_mom[2] )1022 tracker_plots['pz_residual_p'].Fill( P_mc, res_mom[2] )
1018 tracker_plots['p_residual_p'].Fill( P_mc, P_res )1023 tracker_plots['p_residual_p'].Fill( P_mc, P_res )
1024 tracker_plots['dpp_residual_p'].Fill( P_mc, P_res/P_mc )
10191025
10201026
1021 tracker_plots['x_residual_pz'].Fill( Pz_mc, res_pos[0] )1027 tracker_plots['x_residual_pz'].Fill( Pz_mc, res_pos[0] )
@@ -1230,6 +1236,45 @@
1230 plot_dict[tracker][component+plot_axis+'_bias'] = bias_graph1236 plot_dict[tracker][component+plot_axis+'_bias'] = bias_graph
12311237
12321238
1239 for tracker in [ "upstream", "downstream" ] :
1240 for a_plot in [ "dpp_residual_p" ] :
1241 plot = plot_dict[tracker][a_plot]
1242
1243 rms_error = array.array( 'd' )
1244 bin_size = array.array( 'd' )
1245 bins = array.array( 'd' )
1246 rms = array.array( 'd' )
1247 mean = array.array( 'd' )
1248 mean_error = array.array( 'd' )
1249
1250 width = plot.GetXaxis().GetBinWidth(1)
1251 for i in range( 0, plot.GetXaxis().GetNbins() ) :
1252 projection = plot.ProjectionY( a_plot+'_pro_'+str(i), i, (i+1) )
1253
1254 plot_mean = plot.GetXaxis().GetBinCenter( i ) + width
1255 pro_mean, pro_mean_err, pro_std, pro_std_err = \
1256 analysis.tools.fit_gaussian(projection)
1257
1258 bin_size.append( width*0.5 )
1259 bins.append( plot_mean )
1260 rms.append( pro_std )
1261 rms_error.append( pro_std_err )
1262 mean.append( pro_mean )
1263 mean_error.append( pro_mean_err )
1264
1265 if len(bins) != 0 :
1266 resolution_graph = ROOT.TGraphErrors( len(bins), \
1267 bins, rms, bin_size, rms_error )
1268 bias_graph = ROOT.TGraphErrors( len(bins), \
1269 bins, mean, bin_size, mean_error )
1270 else :
1271 resolution_graph = None
1272 bias_graph = None
1273
1274 plot_dict[tracker][a_plot+'_resolution'] = resolution_graph
1275 plot_dict[tracker][a_plot+'_bias'] = bias_graph
1276
1277
12331278
1234 for tracker in [ "upstream", "downstream" ] :1279 for tracker in [ "upstream", "downstream" ] :
1235# for component in [ "pt_", "pz_", ] :1280# for component in [ "pt_", "pz_", ] :
12361281
=== modified file 'src/common_py/analysis/__init__.py'
--- src/common_py/analysis/__init__.py 2017-02-17 12:38:12 +0000
+++ src/common_py/analysis/__init__.py 2018-05-09 09:31:35 +0000
@@ -31,4 +31,5 @@
31import analysis.hit_types31import analysis.hit_types
32import analysis.inspectors32import analysis.inspectors
33import analysis.scifitools # pylint: disable = E040133import analysis.scifitools # pylint: disable = E0401
34import analysis.systematic_error_estimation
3435
3536
=== added file 'src/common_py/analysis/analysis_track.py'
--- src/common_py/analysis/analysis_track.py 1970-01-01 00:00:00 +0000
+++ src/common_py/analysis/analysis_track.py 2018-05-09 09:31:35 +0000
@@ -0,0 +1,141 @@
1#!/usr/bin/env python
2# This file is part of MAUS: http://micewww.pp.rl.ac.uk/projects/maus
3#
4# MAUS is free software: you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation, either version 3 of the License, or
7# (at your option) any later version.
8#
9# MAUS is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with MAUS. If not, see <http://www.gnu.org/licenses/>.
16#
17
18"""
19 Defines the AnalysisTrack class.
20
21 A generic track class that can be used to simplify the analysis of track data.
22"""
23
24# pylint: disable = W0311, R0902, R0904, R0913, C0103, W0102
25
26class AnalysisTrack() :
27 """
28 A simple, unified class that holds the essential information, when copied
29 from MAUS data types, during various analyses.
30
31 Some syntax was borrowed from XBoa for simplicity and because I like the
32 interface!
33 """
34 def __init__( self, trackpoints={}, chisq=0.0, ndf=0, p_value=1.0, status=0 ) :
35 """
36 Initialise object.
37 """
38 self.__trackpoints = trackpoints
39 self.__chisq = chisq
40 self.__ndf = ndf
41 self.__p_value = p_value
42 self.__status = status
43
44
45 def __getitem__(self, index) :
46 """
47 Return a trackpoint from the track at index ``index''
48 """
49 return self.__trackpoints[index]
50
51
52 def __setitem__(self, index, item) :
53 """
54 Set the value of the trackpoint in the track at index ``index''
55 """
56 self.__trackpoints[index] = item
57
58
59 def __len__(self) :
60 """
61 Return the number of trackpoints in the track>
62 """
63 return len(self.__trackpoints)
64
65
66 def get_trackpoint( self, index ) :
67 """
68 Return a trackpoint from the track at index ``index''
69 """
70 return self.__trackpoints[index]
71
72
73 def set_trackpoint( self, index, item ) :
74 """
75 Set the value of the trackpoint in the track at index ``index''
76 """
77 self.__trackpoints[index] = item
78
79
80 def get_p_value( self ) :
81 """
82 Return the track p-value
83 """
84 return self.__p_value
85
86
87 def set_p_value( self, pvalue ) :
88 """
89 Set the track p-value
90 """
91 self.__p_value = pvalue
92
93
94 def get_chisq( self ) :
95 """
96 Return the track chisq squared
97 """
98 return self.__chisq
99
100
101 def set_chisq( self, chisq ) :
102 """
103 Set the track chisq squared
104 """
105 self.__chisq = chisq
106
107
108 def get_ndf( self ) :
109 """
110 Get the track No. Degrees of Freedom
111 """
112 return self.__ndf
113
114
115 def set_ndf( self, ndf ) :
116 """
117 Set the track No. Degrees of Freedom
118 """
119 self.__ndf = ndf
120
121
122 def get_chisq_ndf(self) :
123 """
124 Get the track Chisquared / No. Degrees of Freedom
125 """
126 return self.__chisq / self.__ndf
127
128
129 def set_status(self, status) :
130 """
131 Set a undefined status flag
132 """
133 self.__status = status
134
135
136 def get_status(self) :
137 """
138 Get the status flag
139 """
140 return self.__status
141
0142
=== modified file 'src/common_py/analysis/hit_types.py'
--- src/common_py/analysis/hit_types.py 2017-08-15 16:19:42 +0000
+++ src/common_py/analysis/hit_types.py 2018-05-09 09:31:35 +0000
@@ -23,7 +23,7 @@
23 Note that this class is used by the add_hit meth23 Note that this class is used by the add_hit meth
24"""24"""
2525
26# pylint: disable = W0311, R0902, R0904, R0913, C010326# pylint: disable = W0311, R0902, R0904, R0913, C0103, R0914, R0915
2727
28import math28import math
29import analysis.tools as tools29import analysis.tools as tools
@@ -40,14 +40,15 @@
40 px = 0.0, py = 0.0, pz = 0.0, station = 0, \40 px = 0.0, py = 0.0, pz = 0.0, station = 0, \
41 time = 0.0, mass = 105.6583715, p_value = 1.0, pid = 13, \41 time = 0.0, mass = 105.6583715, p_value = 1.0, pid = 13, \
42 weight = 1.0, \42 weight = 1.0, \
43 scifi_track_point = None, virtual_track_point = None ) :43 scifi_track_point = None, virtual_track_point = None, \
44 global_track_point = None ) :
44 """45 """
45 Initialise the object. this can be done in three ways:46 Initialise the object. this can be done in three ways:
46 1. Specify all components by hand47 1. Specify all components by hand
47 2. Build from a sci-fr trackpoint48 2. Build from a sci-fr trackpoint
48 3. Build from a virtual trackpoint49 3. Build from a virtual trackpoint
49 """50 """
50 if scifi_track_point is None and virtual_track_point is None :51 if scifi_track_point is None and virtual_track_point is None and global_track_point is None :
51 self.__x = x52 self.__x = x
52 self.__y = y53 self.__y = y
53 self.__z = z54 self.__z = z
@@ -62,7 +63,7 @@
62 self.__weight = weight63 self.__weight = weight
63# self.__reference = reference64# self.__reference = reference
6465
65 elif scifi_track_point is not None and virtual_track_point is None :66 elif scifi_track_point is not None and virtual_track_point is None and global_track_point is None :
66 self.__x = scifi_track_point.pos().x()67 self.__x = scifi_track_point.pos().x()
67 self.__y = scifi_track_point.pos().y()68 self.__y = scifi_track_point.pos().y()
68 self.__z = scifi_track_point.pos().z()69 self.__z = scifi_track_point.pos().z()
@@ -82,7 +83,7 @@
82# else :83# else :
83# self.__reference = reference84# self.__reference = reference
8485
85 elif scifi_track_point is None and virtual_track_point is not None :86 elif scifi_track_point is None and virtual_track_point is not None and global_track_point is None :
86 self.__x = virtual_track_point.GetPosition().x()87 self.__x = virtual_track_point.GetPosition().x()
87 self.__y = virtual_track_point.GetPosition().y()88 self.__y = virtual_track_point.GetPosition().y()
88 self.__z = virtual_track_point.GetPosition().z()89 self.__z = virtual_track_point.GetPosition().z()
@@ -101,6 +102,20 @@
101# else :102# else :
102# self.__reference = reference103# self.__reference = reference
103104
105 elif scifi_track_point is None and virtual_track_point is None and global_track_point is not None :
106 self.__x = global_track_point.get_position().X()
107 self.__y = global_track_point.get_position().Y()
108 self.__z = global_track_point.get_position().Z()
109 self.__px = global_track_point.get_momentum().X()
110 self.__py = global_track_point.get_momentum().Y()
111 self.__pz = global_track_point.get_momentum().Z()
112 self.__time = global_track_point.get_position().T()
113 self.__mass = mass
114 self.__p_value = p_value
115 self.__pid = pid
116 self.__station = station
117 self.__weight = weight
118
104 else :119 else :
105 print "WTF!"120 print "WTF!"
106 raise ValueError( 'Please supply precisely one of "virtual_track_point"'+\121 raise ValueError( 'Please supply precisely one of "virtual_track_point"'+\
@@ -388,3 +403,200 @@
388403
389 for key, val in __hit_get_variables.iteritems() :404 for key, val in __hit_get_variables.iteritems() :
390 __hit_get_keys.append( key )405 __hit_get_keys.append( key )
406
407
408################################################################################
409
410
411class AnalysisSpacePoint() :
412 """
413 A simple, unified class that holds the essential information, when copied
414 from MAUS data types, during various analyses.
415
416 Some syntax was borrowed from XBoa for simplicity and because I like the
417 interface!
418 """
419 def __init__( self, x = 0.0, y = 0.0, z = 0.0, station = 0, \
420 time = 0.0, weight = 1.0, tof = None, scifi = None ) :
421 """
422 Initialise the object. this can be done in three ways:
423 1. Specify all components by hand
424 2. Build from a sci-fr trackpoint
425 3. Build from a virtual trackpoint
426 """
427 if tof is None and scifi is None :
428 self.__x = x
429 self.__y = y
430 self.__z = z
431 self.__time = time
432 self.__station = station
433 self.__weight = weight
434
435 elif scifi is not None and tof is None :
436 self.__x = scifi.pos().x()
437 self.__y = scifi.pos().y()
438 self.__z = scifi.pos().z()
439 self.__time = time
440 self.__station = tools.calculate_plane_id(scifi.tracker(), \
441 scifi.station(), 1)
442 self.__weight = weight
443
444 elif scifi is None and tof is not None :
445 self.__x = tof.GetGlobalPosX()
446 self.__y = tof.GetGlobalPosY()
447 self.__z = tof.GetGlobalPosZ()
448 self.__time = tof.GetTime()
449 self.__station = tof.GetStation()
450 self.__weight = weight
451
452 else :
453 print "WTF!"
454 raise ValueError( 'Please supply precisely one of "virtual_track_point"'+\
455 ' or "scifi_track_point", or specify all values explicitly.' )
456
457
458 def __str__( self ) :
459 """
460 Return a string of the parameters.
461 Mostly useful for debuging.
462 """
463 return '(' + str( self.__x ) + ',' +\
464 str( self.__y ) + ',' +\
465 str( self.__z ) + '):[' +\
466 str( self.__time ) +\
467 str( self.__station ) + ']'
468
469
470 def __repr__( self ) :
471 """
472 Return a string of the parameters.
473 Mostly useful for debuging.
474 """
475 return '(' + str( self.__x ) + ',' +\
476 str( self.__y ) + ',' +\
477 str( self.__z ) + '):[' +\
478 str( self.__time ) +\
479 str( self.__station ) + ']'
480
481
482
483 def set_x( self, val ) :
484 """
485 Set the x position
486 """
487 self.__x = val
488
489 def set_y( self, val ) :
490 """
491 Set the y position
492 """
493 self.__y = val
494
495 def set_z( self, val ) :
496 """
497 Set the z position
498 """
499 self.__z = val
500
501 def set_time( self, val ) :
502 """
503 Set the time
504 """
505 self.__time = val
506
507 def set_station( self, station ) :
508 """
509 Set the station ID
510 """
511 self.__station = station
512
513 def set_weight( self, w ) :
514 """
515 Set the statistical weight of the hit
516 """
517 self.__weight = w
518
519
520 def get_x( self ) :
521 """
522 Get the x position
523 """
524 return self.__x
525
526 def get_y( self ) :
527 """
528 Get the y position
529 """
530 return self.__y
531
532 def get_z( self ) :
533 """
534 Get the z position
535 """
536 return self.__z
537
538 def get_time( self ) :
539 """
540 Get the time
541 """
542 return self.__time
543
544 def get_r( self ) :
545 """
546 Get the position radius
547 """
548 return math.sqrt( self.__x**2 + self.__y**2 )
549
550 def get_phi( self ) :
551 """
552 Get the Phi angle (Position)
553 """
554 return math.atan2( self.__y, self.__x )
555
556 def get_station( self ) :
557 """
558 Get the particle ID
559 """
560 return self.__station
561
562
563 def get_weight( self ) :
564 """
565 Return the statistical weight of the hit
566 """
567 return self.__weight
568
569
570 def get( self, string ) :
571 """
572 Return the value of the variable described in the string
573 """
574 if not string in AnalysisSpacePoint.__hit_get_keys :
575 return None
576 else :
577 return AnalysisSpacePoint.__hit_get_variables[ string ]( self )
578
579
580 def get_as_vector( self ) :
581 """
582 Returns the 6D phase-space vector corresponding to the particle.
583 [t, x, y, z]
584 """
585 return [self.__time, self.__x, self.__y, self.__z]
586
587
588 def get_variable_list() :
589 """
590 Return the list of variables that the user can request
591 """
592 return AnalysisSpacePoint.__hit_get_keys
593 get_variable_list = staticmethod( get_variable_list )
594
595
596
597 __hit_get_keys = []
598 __hit_get_variables = { 'x':get_x, 'y':get_y, 'z':get_z,
599 't':get_time, 'r':get_r, 'station':get_station }
600
601 for key, val in __hit_get_variables.iteritems() :
602 __hit_get_keys.append( key )
391603
=== modified file 'src/common_py/analysis/inspectors.py'
--- src/common_py/analysis/inspectors.py 2017-08-15 16:19:42 +0000
+++ src/common_py/analysis/inspectors.py 2018-05-09 09:31:35 +0000
@@ -82,13 +82,13 @@
82 82
83 self.emittance_plot = ROOT.TH1F(\83 self.emittance_plot = ROOT.TH1F(\
84 'inspected_emittance_{0}'.format(self.plane), 'Emittance', \84 'inspected_emittance_{0}'.format(self.plane), 'Emittance', \
85 1000, 0.0, 20.0)85 5000, 0.0, 20.0)
86 self.emittance_x_plot = ROOT.TH1F(\86 self.emittance_x_plot = ROOT.TH1F(\
87 'inspected_emittance_x_{0}'.format(self.plane), 'X Emittance', \87 'inspected_emittance_x_{0}'.format(self.plane), 'X Emittance', \
88 1000, 0.0, 20.0)88 5000, 0.0, 20.0)
89 self.emittance_y_plot = ROOT.TH1F(\89 self.emittance_y_plot = ROOT.TH1F(\
90 'inspected_emittance_y_{0}'.format(self.plane), 'Y Emittance', \90 'inspected_emittance_y_{0}'.format(self.plane), 'Y Emittance', \
91 1000, 0.0, 20.0)91 5000, 0.0, 20.0)
92 self.alpha_plot = ROOT.TH1F(\92 self.alpha_plot = ROOT.TH1F(\
93 'inspected_alpha_{0}'.format(self.plane), 'Alpha Function', \93 'inspected_alpha_{0}'.format(self.plane), 'Alpha Function', \
94 2000, -5.0, 5.0)94 2000, -5.0, 5.0)
@@ -109,11 +109,11 @@
109 3000, 0.0, 3000.0)109 3000, 0.0, 3000.0)
110 self.total_momentum_plot = ROOT.TH1F(\110 self.total_momentum_plot = ROOT.TH1F(\
111 'inspected_total_momentum_{0}'.format(self.plane), 'Momentum', \111 'inspected_total_momentum_{0}'.format(self.plane), 'Momentum', \
112 1000, 0.0, 500.0)112 2000, 0.0, 500.0)
113113
114 self.amplitude_plot = ROOT.TH1F(\114 self.amplitude_plot = ROOT.TH1F(\
115 'single_particle_amplitudes_{0}'.format(self.plane), 'Amplitude', \115 'single_particle_amplitudes_{0}'.format(self.plane), 'Amplitude', \
116 200, 0.0, 100.0)116 500, 0.0, 100.0)
117 self.amplitude_momentum_plot = ROOT.TH2F(\117 self.amplitude_momentum_plot = ROOT.TH2F(\
118 'inspected_A_p_phasespace_{0}'.format(self.plane), 'A-p-Phasespace' , \118 'inspected_A_p_phasespace_{0}'.format(self.plane), 'A-p-Phasespace' , \
119 200, 0.0, 100.0, 260, 130.0, 260.0 )119 200, 0.0, 100.0, 260, 130.0, 260.0 )
@@ -170,15 +170,15 @@
170 if self.ensemble_size > 1 :170 if self.ensemble_size > 1 :
171 self.ensemble_covariance.add_hit(hit)171 self.ensemble_covariance.add_hit(hit)
172172
173 if self.covariance.length() > self.ensemble_size :173 if self.ensemble_covariance.length() > self.ensemble_size :
174 self.fill_plots()174 self.fill_plots()
175175
176176
177 def fill_plots(self) :177 def fill_plots(self) :
178 if ( self.covariance.length() < (self.ensemble_size / 2) ) or self.ensemble_size < 2 :178 if ( self.ensemble_covariance.length() < (self.ensemble_size / 2) ) or self.ensemble_size < 2 :
179 return179 return
180180
181 self.emittance_plot.Fill(self.covariance.get_emittance(\181 self.emittance_plot.Fill(self.ensemble_covariance.get_emittance(\
182 ['x','px','y','py']))182 ['x','px','y','py']))
183 self.emittance_x_plot.Fill(self.ensemble_covariance.get_emittance(['x','px']))183 self.emittance_x_plot.Fill(self.ensemble_covariance.get_emittance(['x','px']))
184 self.emittance_y_plot.Fill(self.ensemble_covariance.get_emittance(['y','py']))184 self.emittance_y_plot.Fill(self.ensemble_covariance.get_emittance(['y','py']))
@@ -446,3 +446,86 @@
446446
447 return ins_data447 return ins_data
448448
449
450####################################################################################################
451
452
453class EmittanceSystematicErrorInspector() :
454
455 def __init__(self, plane_id, ensemble_size) :
456 if ensemble_size < 2 :
457 raise ValueError("Ensemble size is too small.")
458
459 self.plane = plane_id
460 self.ensemble_size = ensemble_size
461 self.covariance = analysis.covariances.CovarianceMatrix()
462 self.true_covariance = analysis.covariances.CovarianceMatrix()
463 self.systematic_error_calculator = analysis.systematic_error_estimation.chi_squared_estimator()
464 self.true_emittances = []
465
466
467 def add_hit(self, hit, true_hit) :
468 self.covariance.add_hit(hit)
469 self.true_covariance.add_hit(true_hit)
470
471 if self.covariance.length() > self.ensemble_size :
472 self.fill_calculator()
473
474
475 def fill_calculator(self) :
476 if ( self.covariance.length() < (self.ensemble_size / 2) ) :
477 return
478
479 emittance = self.covariance.get_emittance(['x','px','y','py'])
480 true_emittance = self.true_covariance.get_emittance(['x','px','y','py'])
481
482 self.true_emittances.append( ( true_emittance, true_emittance / math.sqrt( 2.0*(self.true_covariance.length() - 1) ) ) )
483 self.systematic_error_calculator.add_value( emittance, emittance / math.sqrt( 2.0*(self.covariance.length() - 1) ) )
484
485 self.covariance.clear()
486 self.true_covariance.clear()
487
488
489 def get_current_ensemble_size(self) :
490 return self.covariance.length()
491
492
493 def get_systematic_error(self) :
494 num_measurements = len(self.systematic_error_calculator)
495
496 if num_measurements > 0 :
497 numer = 0.0
498 denom = 0.0
499 for meas, weight in self.true_emittances :
500 numer += meas / (weight**2)
501 denom += 1.0 / (weight**2)
502 true_emittance = numer / denom
503
504 bias = self.systematic_error_calculator.calculate_mean() - true_emittance
505 error = math.sqrt(self.systematic_error_calculator.calculate_variance()/num_measurements)
506 bias_lower = bias - error
507 bias_upper = bias + error
508
509 result = self.systematic_error_calculator.calculate_systematic_error()
510 lower, upper = self.systematic_error_calculator.calculate_bounds( result )
511 else :
512 bias = 0.0
513 error = 0.0
514 bias_lower = 0.0
515 bias_upper = 0.0
516
517 result = 0.0
518 lower = 0.0
519 upper = 0.0
520
521 return bias, bias_lower, bias_upper, result, lower, upper
522
523
524 def get_data_dictionary(self) :
525 return {}
526
527
528 def get_plot_dictionary(self) :
529 return self.systematic_error_calculator.get_plots()
530
531
449532
=== added file 'src/common_py/analysis/systematic_error_estimation.py'
--- src/common_py/analysis/systematic_error_estimation.py 1970-01-01 00:00:00 +0000
+++ src/common_py/analysis/systematic_error_estimation.py 2018-05-09 09:31:35 +0000
@@ -0,0 +1,247 @@
1#!/usr/bin/env python
2
3# This file is part of MAUS: http://micewww.pp.rl.ac.uk/projects/maus
4#
5# MAUS is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# MAUS is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with MAUS. If not, see <http://www.gnu.org/licenses/>.
17#
18
19"""
20 Some classes for the calculation of systematic errors.
21
22 Inspiration is from:
23 L Lyons 1992 J. Phys. A: Math. Gen. 25 1967
24"""
25
26# pylint: disable = W0311, E1101, W0102, R0902, C0103, R0904
27
28import math
29import ROOT
30
31import scipy
32import scipy.optimize
33
34
35class chi_squared_estimator(object) :
36 """
37 Performs a chi-squared estiamtion of the systematic error based on pairs of values + errors.
38 """
39
40 def __init__(self, unique_name="chisq_error_estimation") :
41 """
42 Initialise empty object.
43 Option to set the convergence criteria for the chisq-minimisation routine
44 """
45 self.__unique_name = unique_name
46 self.__measurements = []
47 self.__errors = []
48 self.__systematic_error = 0.0
49 self.__mean = 0.0
50
51
52 def __len__(self) :
53 """
54 Return the number of measurements
55 """
56 return len(self.__measurements)
57
58
59 def add_value(self, measurement, standard_error) :
60 """
61 Add a measurement and its standard error to the object
62 """
63 self.__measurements.append(measurement)
64 self.__errors.append(standard_error)
65
66
67 def _calculate_chisquared(self) :
68 """
69 Private function: calculate the chisquared of the measurements
70 """
71 total = 0.0
72 s_sq = self.__systematic_error**2
73
74 for meas, error in zip(self.__measurements, self.__errors) :
75 total += (meas - self.__mean)**2 / (error*error + s_sq)
76
77 return total
78
79
80 def _calculate_mean(self) :
81 """
82 Private function: calculate the weighted mean of the measurements
83 """
84 numerator = 0.0
85 denominator = 0.0
86# s_sq = self.__systematic_error**2
87
88 for meas, error in zip(self.__measurements, self.__errors) :
89 numerator += meas / (error*error)
90 denominator += 1.0 / (error*error)
91
92 return numerator / denominator
93
94
95 def calculate_mean(self) :
96 """
97 Calculate the weighted mean of the measurements assuming no systematic error.
98 """
99 self.__systematic_error = 0.0
100 return self._calculate_mean()
101
102
103 def calculate_variance(self) :
104 """
105 Calculate the variance of the measurements assuming no systematic error.
106 """
107 mean = self.calculate_mean()
108 summation = 0.0
109 for meas in self.__measurements :
110 summation += (meas - mean)**2
111
112 return summation / len(self.__measurements)
113
114
115 def _constant_error_systematic(self, get_mean, NDOF, stat_error) :
116 """
117 Private function: Calculate the systematic error assuming every measurement has the
118 same statistical error, and the mean is already assumed.
119
120 This permits a simpler equation to solve.
121 """
122 self.__mean = get_mean()
123 total = 0.0
124
125 for meas in self.__measurements :
126 total += (meas - self.__mean)**2
127
128 diff = ( total / NDOF ) - stat_error**2
129
130 if diff <= 0.0 :
131 return 0.0
132 else :
133 return math.sqrt(diff)
134
135
136 def calculate_bounds(self, systematic_estimate, mean=None) :
137 """
138 Estimate an asymmetric error bar based on the chisquared distribution.
139
140 Changes the estimate of the systematic error until the delta chi-squared reaches the
141 equivalent of a 1-sigma deviation (central 68%).
142 """
143 lower_bar = -1.0e300 # Ridonkulous starting values
144 upper_bar = 1.0e300
145
146 get_mean = None
147
148 if mean is None : # Estimate mean from the measurements
149 get_mean = self._calculate_mean
150 NDOF = len(self.__measurements) - 1
151
152 else : # If the mean is specified we gain a degree of freedom.
153 get_mean = lambda : mean
154 NDOF = len(self.__measurements)
155
156
157 bounds = [(0.0, None)]
158
159 limit = scipy.stats.chi2.ppf(0.16, NDOF)
160 upper_bar = scipy.optimize.minimize(self._minimize_me, [systematic_estimate], args=(limit, get_mean), method="TNC", bounds=bounds)
161
162 limit = scipy.stats.chi2.ppf(0.84, NDOF)
163 lower_bar = scipy.optimize.minimize(self._minimize_me, [systematic_estimate], args=(limit, get_mean), method="TNC", bounds=bounds)
164
165 return lower_bar.x[0], upper_bar.x[0]
166
167
168 def calculate_systematic_error(self, mean=None, error_assumption=None) :
169 """
170 The main function - returns the systematic error given the data and measurements stored in
171 the class.
172
173 Specifying the mean stops the mean from being calculated on the fly from the data, this
174 slightly changes th behaviour - the mean is no longer a function of the systematic error and
175 the number of degrees of freedom is different.
176
177 Specifying the error_assumption, assumes that every measurement has the same (specified)
178 error. This allows for a faster calculation of the systematic error.
179
180 Specifying both allows the for systematic error to be calculated trivially.
181 """
182 get_mean = None
183 NDOF = 0
184 self.__systematic_error = 0.0
185
186 if mean is None : # Estimate mean from the measurements
187 get_mean = self._calculate_mean
188 NDOF = len(self.__measurements) - 1
189
190 else : # If the mean is specified we gain a degree of freedom.
191 get_mean = lambda : mean
192 NDOF = len(self.__measurements)
193
194
195 if error_assumption is not None : # Assume all statistical errors are the
196 # same. Shortcut!
197 return self._constant_error_systematic(get_mean, NDOF, error_assumption)
198
199
200 bounds = [(0.0, None)]
201 estimate = 0.0
202 result = scipy.optimize.minimize(self._minimize_me, [estimate], args=(NDOF, get_mean), method="TNC", bounds=bounds)
203
204 self.__systematic_error = result.x[0]
205
206 return self.__systematic_error
207
208
209 def _minimize_me(self, parameters, ndof, get_mean) :
210 """
211 Minimize function for SciPy.Optimize.
212
213 Calculates the squared residual of the Chi-squared value and number of degrees of freedom.
214 """
215 self.__systematic_error = parameters[0]
216 self.__mean = get_mean()
217 chisq = self._calculate_chisquared()
218 return (chisq - ndof)**2
219
220
221 def get_plots(self) :
222 """
223 Return a dictionary containing histograms of the data that was stored.
224 """
225 if len(self.__measurements) == 0 :
226 return {}
227
228 min_val = min(self.__measurements)
229 max_val = max(self.__measurements)
230 total_weight = 0.0
231
232 for error in self.__errors :
233 total_weight += 1.0 / (error**2)
234
235 hist = ROOT.TH1F(self.__unique_name, "", 100, min_val, max_val)
236 weighted_hist = ROOT.TH1F(self.__unique_name+'-weighted', "", 100, min_val, max_val)
237
238 for value, error in zip( self.__measurements, self.__errors ) :
239 hist.Fill(value)
240 weighted_hist.Fill(value, (1.0/(error**2))/total_weight)
241
242 plots = {}
243 plots['measurements'] = hist
244 plots['weighted_measurements'] = weighted_hist
245
246 return plots
247
0248
=== modified file 'src/common_py/framework/single_thread.py'
--- src/common_py/framework/single_thread.py 2018-02-14 14:17:17 +0000
+++ src/common_py/framework/single_thread.py 2018-05-09 09:31:35 +0000
@@ -201,15 +201,16 @@
201 # done with tranform-merge, now write it out201 # done with tranform-merge, now write it out
202 self.outputer.save(redevent)202 self.outputer.save(redevent)
203 try:203 try:
204 del redevent
205 except:
206 pass
207 # if we converted to a different representation, delete the old one
208 try:
209 maus_cpp.converter.del_data_repr(event)204 maus_cpp.converter.del_data_repr(event)
205 except TypeError: # pylint: disable = W0702
206 print "Failed to delete MAUS::Data object."
207 try:
210 maus_cpp.converter.del_data_repr(redevent)208 maus_cpp.converter.del_data_repr(redevent)
211 except: # pylint: disable = W0702209 except TypeError: # pylint: disable = W0702
212 pass210 try: # If we returned a different datatype, try to delete it anyway.
211 del redevent
212 except:
213 print "Failed to delete Reduced Data object."
213214
214 def start_of_run(self, new_run_number):215 def start_of_run(self, new_run_number):
215 """216 """

Subscribers

People subscribed via source and target branches