ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/UserCode/grimes/L1Menu/src/ReducedMenuSample.cpp
(Generate patch)

Comparing UserCode/grimes/L1Menu/src/ReducedMenuSample.cpp (file contents):
Revision 1.1 by grimes, Wed May 29 00:01:02 2013 UTC vs.
Revision 1.8 by grimes, Fri Jun 7 14:22:05 2013 UTC

# Line 1 | Line 1
1   #include "l1menu/ReducedMenuSample.h"
2  
3   #include <vector>
4 + #include <stdexcept>
5 + #include <fcntl.h>
6 + #include <algorithm>
7 + #include <iostream>
8   #include "l1menu/IReducedEvent.h"
9   #include "l1menu/MenuSample.h"
10   #include "l1menu/TriggerMenu.h"
11 + #include "l1menu/ITrigger.h"
12   #include "l1menu/tools.h"
13 + #include "protobuf/l1menu.pb.h"
14 + #include <google/protobuf/io/zero_copy_stream_impl.h>
15 + #include <google/protobuf/io/gzip_stream.h>
16  
17   namespace // unnamed namespace
18   {
# Line 12 | Line 20 | namespace // unnamed namespace
20          {
21          public:
22                  virtual ~ReducedEventImplementation() {}
23 <                virtual float parameterValue( size_t parameterNumber ) { return pThresholdValues->at(parameterNumber); }
24 <                virtual size_t parameterNumber( const l1menu::ITrigger& trigger, const std::string& parameterName ) { return 0; }
25 <                virtual float weight() const { return 1; }
26 <                std::vector<float>* pThresholdValues;
23 >                virtual float parameterValue( size_t parameterNumber ) const {  return pProtobufEvent->threshold(parameterNumber); }
24 >                virtual float weight() const { if( pProtobufEvent->has_weight() ) return pProtobufEvent->weight(); else return 1; }
25 >                void setWeight( float newWeight ) { pProtobufEvent->set_weight(newWeight); }
26 >                l1menuprotobuf::Event* pProtobufEvent;
27 >        };
28 >
29 >        /** @brief Sentry that closes a Unix file descriptor when it goes out of scope.
30 >         * @author Mark Grimes (mark.grimes@bristol.ac.uk)
31 >         * @date 07/Jun/2013
32 >         */
33 >        class UnixFileSentry
34 >        {
35 >        public:
36 >                UnixFileSentry( int fileDescriptor ) : fileDescriptor_(fileDescriptor) {}
37 >                ~UnixFileSentry() { close(fileDescriptor_); }
38 >        private:
39 >                int fileDescriptor_;
40          };
41   }
42  
# Line 28 | Line 49 | namespace l1menu
49           */
50          class ReducedMenuSamplePrivateMembers
51          {
52 +        private:
53 +                l1menu::TriggerMenu mutableTriggerMenu_;
54          public:
55 <                ReducedMenuSamplePrivateMembers( size_t newNumberOfEvents, size_t numberOfParameters )
56 <                        : thresholdsForAllEvents( newNumberOfEvents, std::vector<float>(numberOfParameters) )
57 <                {
35 <                        // No operation besides the initialiser list
36 <                }
37 <                std::vector< std::vector<float> > thresholdsForAllEvents;
55 >                ReducedMenuSamplePrivateMembers( const l1menu::TriggerMenu& newTriggerMenu );
56 >                ReducedMenuSamplePrivateMembers( const std::string& filename );
57 >                void copyMenuToProtobufSample();
58                  ::ReducedEventImplementation event;
59 <                size_t numberOfEvents;
59 >                const l1menu::TriggerMenu& triggerMenu;
60 >                l1menuprotobuf::SampleHeader protobufSampleHeader;
61 >                // Protobuf doesn't implement move semantics so I'll use pointers
62 >                std::vector<std::unique_ptr<l1menuprotobuf::Run> > protobufRuns;
63 >                const static int EVENTS_PER_RUN;
64 >                const static char PROTOBUF_MESSAGE_DELIMETER;
65 >                const static std::string FILE_FORMAT_MAGIC_NUMBER;
66          };
67 +
68 +        const int ReducedMenuSamplePrivateMembers::EVENTS_PER_RUN=20000;
69 +        const char ReducedMenuSamplePrivateMembers::PROTOBUF_MESSAGE_DELIMETER='\n';
70 +        const std::string ReducedMenuSamplePrivateMembers::FILE_FORMAT_MAGIC_NUMBER="l1menuReducedMenuSample";
71   }
72  
73 < l1menu::ReducedMenuSample::ReducedMenuSample( const l1menu::MenuSample& originalSample, const l1menu::TriggerMenu& triggerMenu )
73 > l1menu::ReducedMenuSamplePrivateMembers::ReducedMenuSamplePrivateMembers( const l1menu::TriggerMenu& newTriggerMenu )
74 >        : mutableTriggerMenu_( newTriggerMenu ), triggerMenu( mutableTriggerMenu_ )
75   {
76 <        size_t numberOfEvents=originalSample.numberOfEvents();
77 <        // Need to find out how many parameters there are for each event. Basically the sum
78 <        // of the number of thresholds for all triggers.
79 <        size_t numberOfParameters=0;
76 >        GOOGLE_PROTOBUF_VERIFY_VERSION;
77 >
78 >        // I need to copy the details of the trigger menu into the protobuf storage.
79 >        // This means I'm holding a duplicate, but I need it to write the sample to a
80 >        // protobuf file, so I might as well do it now.
81          for( size_t triggerNumber=0; triggerNumber<triggerMenu.numberOfTriggers(); ++triggerNumber )
82          {
83                  const l1menu::ITrigger& trigger=triggerMenu.getTrigger(triggerNumber);
84 <                numberOfParameters+=l1menu::getThresholdNames(trigger).size();
84 >
85 >                l1menuprotobuf::Trigger* pProtobufTrigger=protobufSampleHeader.add_trigger();
86 >                pProtobufTrigger->set_name( trigger.name() );
87 >                pProtobufTrigger->set_version( trigger.version() );
88 >
89 >                // Record all of the parameters. It's not strictly necessary to record the values
90 >                // of the parameters that are recorded for each event, but I might as well so that
91 >                // the trigger menu is loaded exactly as it was saved.
92 >                const auto parameterNames=trigger.parameterNames();
93 >                for( const auto& parameterName : parameterNames )
94 >                {
95 >                        l1menuprotobuf::Trigger_TriggerParameter* pProtobufParameter=pProtobufTrigger->add_parameter();
96 >                        pProtobufParameter->set_name(parameterName);
97 >                        pProtobufParameter->set_value( trigger.parameter(parameterName) );
98 >                }
99 >
100 >                // Make a note of the names of the parameters that are recorded for each event. For this
101 >                // I'm just recording the parameters that refer to the thresholds.
102 >                const auto thresholdNames=l1menu::getThresholdNames(trigger);
103 >                for( const auto& thresholdName : thresholdNames ) pProtobufTrigger->add_varying_parameter(thresholdName);
104 >
105 >        } // end of loop over triggers
106 >
107 >        // Always make sure there is at least one Run ready to be added to
108 >        std::unique_ptr<l1menuprotobuf::Run> pNewRun( new l1menuprotobuf::Run );
109 >        protobufRuns.push_back( std::move( pNewRun ) );
110 >
111 > }
112 >
113 > l1menu::ReducedMenuSamplePrivateMembers::ReducedMenuSamplePrivateMembers( const std::string& filename )
114 >        : triggerMenu(mutableTriggerMenu_)
115 > {
116 >        GOOGLE_PROTOBUF_VERIFY_VERSION;
117 >
118 >        // Open the file with read ability
119 >        int fileDescriptor = open( filename.c_str(), O_RDONLY );
120 >        if( fileDescriptor==0 ) throw std::runtime_error( "ReducedMenuSample initialise from file - couldn't open file" );
121 >        ::UnixFileSentry fileSentry( fileDescriptor ); // Use this as an exception safe way of closing the input file
122 >        google::protobuf::io::FileInputStream fileInput( fileDescriptor );
123 >        google::protobuf::io::GzipInputStream gzipInput( &fileInput );
124 >        google::protobuf::io::CodedInputStream codedInput( &gzipInput );
125 >
126 >        // First read the magic number at the start of the file and make sure it
127 >        // matches what I expect. As a read buffer, I'll create a string the correct
128 >        // size (filled with an arbitrary character) and read straight into that.
129 >        std::string readMagicNumber;
130 >        if( !codedInput.ReadString( &readMagicNumber, FILE_FORMAT_MAGIC_NUMBER.size() ) ) throw std::runtime_error( "ReducedMenuSample initialise from file - error reading magic number" );
131 >        if( readMagicNumber!=FILE_FORMAT_MAGIC_NUMBER ) throw std::runtime_error( "ReducedMenuSample - tried to initialise with a file that is not the correct format" );
132 >
133 >        google::protobuf::uint32 fileformatVersion;
134 >        if( !codedInput.ReadVarint32( &fileformatVersion ) ) throw std::runtime_error( "ReducedMenuSample initialise from file - error reading file format version" );
135 >        // So far I only have (and ever expect to have) one version of the file
136 >        // format, imaginatively versioned "1". You never know though...
137 >        if( fileformatVersion>1 ) std::cerr << "Warning: Attempting to read a ReducedMenuSample with version " << fileformatVersion << " with code that only knows up to version 1." << std::endl;
138 >
139 >        google::protobuf::uint64 messageSize;
140 >
141 >        // Read the size of the header message
142 >        if( !codedInput.ReadVarint64( &messageSize ) ) throw std::runtime_error( "ReducedMenuSample initialise from file - error reading message size for header" );
143 >        google::protobuf::io::CodedInputStream::Limit readLimit=codedInput.PushLimit(messageSize);
144 >        if( !protobufSampleHeader.ParseFromCodedStream( &codedInput ) ) throw std::runtime_error( "ReducedMenuSample initialise from file - some unknown error while reading header" );
145 >        codedInput.PopLimit(readLimit);
146 >
147 >        // Keep looping until there is nothing more to be read from the file.
148 >        while( codedInput.ReadVarint64( &messageSize ) )
149 >        {
150 >                readLimit=codedInput.PushLimit(messageSize);
151 >
152 >                std::unique_ptr<l1menuprotobuf::Run> pNewRun( new l1menuprotobuf::Run );
153 >                if( !pNewRun->ParseFromCodedStream( &codedInput ) ) throw std::runtime_error( "ReducedMenuSample initialise from file - some unknown error while reading run" );
154 >                protobufRuns.push_back( std::move( pNewRun ) );
155 >
156 >                codedInput.PopLimit(readLimit);
157 >        }
158 >
159 >
160 >        // Always make sure there is at least one Run ready to be added to. Later
161 >        // code assumes there is already a run there.
162 >        if( protobufRuns.empty() )
163 >        {
164 >                std::unique_ptr<l1menuprotobuf::Run> pNewRun( new l1menuprotobuf::Run );
165 >                protobufRuns.push_back( std::move( pNewRun ) );
166 >        }
167 >
168 >        // I have all of the information in the protobuf members, but I also need the trigger information
169 >        // in the form of l1menu::TriggerMenu. Copy out the required information.
170 >        for( int triggerNumber=0; triggerNumber<protobufSampleHeader.trigger_size(); ++triggerNumber )
171 >        {
172 >                const l1menuprotobuf::Trigger& inputTrigger=protobufSampleHeader.trigger(triggerNumber);
173 >
174 >                mutableTriggerMenu_.addTrigger( inputTrigger.name(), inputTrigger.version() );
175 >                // Get a reference to the trigger I just created
176 >                l1menu::ITrigger& trigger=mutableTriggerMenu_.getTrigger(triggerNumber);
177 >
178 >                // Run through all of the parameters and set them to what they were
179 >                // when the sample was made.
180 >                for( int parameterNumber=0; parameterNumber<inputTrigger.parameter_size(); ++parameterNumber )
181 >                {
182 >                        const auto& inputParameter=inputTrigger.parameter(parameterNumber);
183 >                        trigger.parameter(inputParameter.name())=inputParameter.value();
184 >                }
185 >
186 >                // I should probably check the threshold names exist. I'll do it another time.
187 >        }
188 >
189 > }
190 >
191 > l1menu::ReducedMenuSample::ReducedMenuSample( const l1menu::MenuSample& originalSample, const l1menu::TriggerMenu& triggerMenu )
192 >        : pImple_( new l1menu::ReducedMenuSamplePrivateMembers( triggerMenu ) )
193 > {
194 >        addSample( originalSample );
195 > }
196 >
197 > l1menu::ReducedMenuSample::ReducedMenuSample( const l1menu::TriggerMenu& triggerMenu )
198 >        : pImple_( new l1menu::ReducedMenuSamplePrivateMembers( triggerMenu ) )
199 > {
200 >        // No operation besides the initialiser list
201 > }
202 >
203 > l1menu::ReducedMenuSample::~ReducedMenuSample()
204 > {
205 >        // No operation. Just need one defined otherwise the default one messes up
206 >        // the unique_ptr deletion because ReducedMenuSamplePrivateMembers isn't
207 >        // defined elsewhere.
208 > }
209 >
210 > void l1menu::ReducedMenuSample::addSample( const l1menu::MenuSample& originalSample )
211 > {
212 >        l1menuprotobuf::Run* pCurrentRun=pImple_->protobufRuns.back().get();
213 >
214 >        for( size_t eventNumber=0; eventNumber<originalSample.numberOfEvents(); ++eventNumber )
215 >        {
216 >                // Split the events up into groups in arbitrary numbers. This is to get around
217 >                // a protobuf aversion to long messages.
218 >                if( pCurrentRun->event_size() > pImple_->EVENTS_PER_RUN )
219 >                {
220 >                        // Gone over the arbitrary limit, so create a new protobuf Run and start
221 >                        // using that instead.
222 >                        std::unique_ptr<l1menuprotobuf::Run> pNewRun( new l1menuprotobuf::Run );
223 >                        pImple_->protobufRuns.push_back( std::move( pNewRun ) );
224 >                        pCurrentRun=pImple_->protobufRuns.back().get();
225 >                }
226 >
227 >                const l1menu::IEvent& event=originalSample.getEvent( eventNumber );
228 >                l1menuprotobuf::Event* pProtobufEvent=pCurrentRun->add_event();
229 >
230 >                // Loop over all of the triggers
231 >                for( size_t triggerNumber=0; triggerNumber<pImple_->triggerMenu.numberOfTriggers(); ++triggerNumber )
232 >                {
233 >                        std::unique_ptr<l1menu::ITrigger> pTrigger=pImple_->triggerMenu.getTriggerCopy(triggerNumber);
234 >                        std::vector<std::string> thresholdNames=getThresholdNames(*pTrigger);
235 >
236 >                        try
237 >                        {
238 >                                setTriggerThresholdsAsTightAsPossible( event, *pTrigger, 0.001 );
239 >                                // Set all of the parameters to match the thresholds in the trigger
240 >                                for( const auto& thresholdName : thresholdNames )
241 >                                {
242 >                                        pProtobufEvent->add_threshold( pTrigger->parameter(thresholdName) );
243 >                                }
244 >                        }
245 >                        catch( std::exception& error )
246 >                        {
247 >                                // setTriggerThresholdsAsTightAsPossible() couldn't find thresholds so record
248 >                                // -1 for everything.
249 >                                // Range based for loop gives me a warning because I don't use the thresholdName.
250 >                                for( size_t index=0; index<thresholdNames.size(); ++index ) pProtobufEvent->add_threshold(-1);
251 >                        } // end of try block that sets the trigger thresholds
252 >
253 >                } // end of loop over triggers
254 >
255 >        } // end of loop over events
256 > }
257 >
258 > l1menu::ReducedMenuSample::ReducedMenuSample( const std::string& filename )
259 >        : pImple_( new l1menu::ReducedMenuSamplePrivateMembers( filename ) )
260 > {
261 >        // No operation except the initialiser list
262 > }
263 >
264 > void l1menu::ReducedMenuSample::saveToFile( const std::string& filename ) const
265 > {
266 >        // Open the file. Parameters are filename, write ability and create, rw-r--r-- permissions.
267 >        int fileDescriptor = open( filename.c_str(), O_WRONLY | O_CREAT, 0644 );
268 >        if( fileDescriptor==0 ) throw std::runtime_error( "ReducedMenuSample save to file - couldn't open file" );
269 >        ::UnixFileSentry fileSentry( fileDescriptor ); // Use this as an exception safe way of closing the output file
270 >
271 >        // Setup the protobuf file handlers
272 >        google::protobuf::io::FileOutputStream fileOutput( fileDescriptor );
273 >        google::protobuf::io::GzipOutputStream gzipOutput( &fileOutput );
274 >        google::protobuf::io::CodedOutputStream codedOutput( &gzipOutput );
275 >
276 >        // Write a magic number at the start of all files
277 >        codedOutput.WriteString( pImple_->FILE_FORMAT_MAGIC_NUMBER );
278 >        // Write an integer that specifies what version of the file format I'm using. I
279 >        // have no intention of changing the format but I might as well keep the option
280 >        // open.
281 >        codedOutput.WriteVarint32( 1 );
282 >
283 >        // Write the size of the header message into the file...
284 >        codedOutput.WriteVarint64( pImple_->protobufSampleHeader.ByteSize() );
285 >        // ...and then write the header
286 >        pImple_->protobufSampleHeader.SerializeToCodedStream( &codedOutput );
287 >
288 >        // Now go through each of the runs and do the same for those
289 >        for( const auto& pRun : pImple_->protobufRuns )
290 >        {
291 >                codedOutput.WriteVarint64( pRun->ByteSize() );
292 >                pRun->SerializeToCodedStream( &codedOutput );
293          }
294  
55        // Now I know how many events there are and how many parameters, I can create the pimple
56        // with the correct parameters.
57        pImple_.reset( new l1menu::ReducedMenuSamplePrivateMembers( numberOfEvents, numberOfParameters ) );
295   }
296  
297   size_t l1menu::ReducedMenuSample::numberOfEvents() const
298   {
299 <        return pImple_->numberOfEvents;
299 >        size_t numberOfEvents=0;
300 >        for( const auto& pRun : pImple_->protobufRuns ) numberOfEvents+=pRun->event_size();
301 >        return numberOfEvents;
302   }
303  
304   const l1menu::IReducedEvent& l1menu::ReducedMenuSample::getEvent( size_t eventNumber ) const
305   {
306 <        pImple_->event.pThresholdValues=&pImple_->thresholdsForAllEvents[eventNumber];
307 <        return pImple_->event;
306 >        for( const auto& pRun : pImple_->protobufRuns )
307 >        {
308 >                if( eventNumber<static_cast<size_t>(pRun->event_size()) )
309 >                {
310 >                        pImple_->event.pProtobufEvent=pRun->mutable_event(eventNumber);
311 >                        return pImple_->event;
312 >                }
313 >                // Event must be in a later run, so reduce the number by how many events
314 >                // were in this run and look again.
315 >                eventNumber-=pRun->event_size();
316 >        }
317 >
318 >        // Should always find the event before getting to this point, so throw an
319 >        // exception if this happens.
320 >        throw std::runtime_error( "ReducedMenuSample::getEvent(eventNumber) was asked for an invalid eventNumber" );
321 > }
322 >
323 > const l1menu::TriggerMenu& l1menu::ReducedMenuSample::getTriggerMenu() const
324 > {
325 >        return pImple_->triggerMenu;
326 > }
327 >
328 > bool l1menu::ReducedMenuSample::containsTrigger( const l1menu::ITrigger& trigger, bool allowOlderVersion ) const
329 > {
330 >        // Loop over all of the triggers in the menu, and see if there is one
331 >        // where the name and version match.
332 >        for( size_t triggerNumber=0; triggerNumber<pImple_->triggerMenu.numberOfTriggers(); ++triggerNumber )
333 >        {
334 >                const l1menu::ITrigger& triggerInMenu=pImple_->triggerMenu.getTrigger(triggerNumber);
335 >                if( triggerInMenu.name()!=trigger.name() ) continue;
336 >                if( allowOlderVersion )
337 >                {
338 >                        if( triggerInMenu.version()>trigger.version() ) continue;
339 >                }
340 >                else
341 >                {
342 >                        if( triggerInMenu.version()!=trigger.version() ) continue;
343 >                }
344 >
345 >                // If control got this far then there is a trigger with the required name
346 >                // and sufficient version. I now need to check all of the non threshold parameters
347 >                // to make sure they match, i.e. make sure the ReducedSample was made with the same
348 >                // eta cuts or whatever.
349 >                // I don't care if the thresholds don't match because that's what's stored in the
350 >                // ReducedMenuSample.
351 >                std::vector<std::string> parameterNames=getNonThresholdParameterNames( trigger );
352 >                bool allParametersMatch=true;
353 >                for( const auto& parameterName : parameterNames )
354 >                {
355 >                        if( trigger.parameter(parameterName)!=triggerInMenu.parameter(parameterName) ) allParametersMatch=false;
356 >                }
357 >
358 >                if( allParametersMatch ) return true;
359 >        } // end of loop over triggers
360 >
361 >        // If control got this far then no trigger was found that matched
362 >        return false;
363 > }
364 >
365 > const std::map<std::string,size_t> l1menu::ReducedMenuSample::getTriggerParameterIdentifiers( const l1menu::ITrigger& trigger, bool allowOlderVersion ) const
366 > {
367 >        std::map<std::string,size_t> returnValue;
368 >
369 >        // Need to find out how many parameters there are for each event. Basically the sum
370 >        // of the number of thresholds for all triggers.
371 >        size_t parameterNumber=0;
372 >        bool triggerWasFound=true;
373 >        for( size_t triggerNumber=0; triggerNumber<pImple_->triggerMenu.numberOfTriggers(); ++triggerNumber )
374 >        {
375 >                const l1menu::ITrigger& triggerInMenu=pImple_->triggerMenu.getTrigger(triggerNumber);
376 >
377 >                triggerWasFound=true; // Set to true, then back to false if any of the tests fail
378 >                // See if this trigger in the menu is the same as the one passed as a parameter
379 >                if( triggerInMenu.name()!=trigger.name() ) triggerWasFound=false;
380 >                if( allowOlderVersion )
381 >                {
382 >                        if( triggerInMenu.version()>trigger.version() ) triggerWasFound=false;
383 >                }
384 >                else
385 >                {
386 >                        if( triggerInMenu.version()!=trigger.version() ) triggerWasFound=false;
387 >                }
388 >
389 >                // If control got this far then there is a trigger with the required name
390 >                // and sufficient version. I now need to check all of the non threshold parameters
391 >                // to make sure they match, i.e. make sure the ReducedSample was made with the same
392 >                // eta cuts or whatever.
393 >                // I don't care if the thresholds don't match because that's what's stored in the
394 >                // ReducedMenuSample.
395 >                if( triggerWasFound ) // Trigger can still fail, but no point doing this check if it already has
396 >                {
397 >                        std::vector<std::string> parameterNames=getNonThresholdParameterNames( trigger );
398 >                        for( const auto& parameterName : parameterNames )
399 >                        {
400 >                                if( trigger.parameter(parameterName)!=triggerInMenu.parameter(parameterName) ) triggerWasFound=false;
401 >                        }
402 >                }
403 >
404 >                std::vector<std::string> thresholdNames=l1menu::getThresholdNames(triggerInMenu);
405 >                if( triggerWasFound )
406 >                {
407 >                        for( const auto& thresholdName : thresholdNames )
408 >                        {
409 >                                returnValue[thresholdName]=parameterNumber;
410 >                                ++parameterNumber;
411 >                        }
412 >                        break;
413 >                }
414 >                else parameterNumber+=thresholdNames.size();
415 >        }
416 >
417 >        // There could conceivably be a trigger that was found but has no thresholds
418 >        // (I guess - it would be a pretty pointless trigger though). To indicate the
419 >        // difference between that and a trigger that wasn't found I'll respectively
420 >        // return the empty vector or throw an exception.
421 >        if( !triggerWasFound ) throw std::runtime_error( "l1menu::ReducedMenuSample::getTriggerParameterIdentifiers() called for a trigger that was not used to create the sample" );
422 >
423 >        return returnValue;
424   }

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines