ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/UserCode/grimes/L1Menu/src/ReducedMenuSample.cpp
Revision: 1.8
Committed: Fri Jun 7 14:22:05 2013 UTC (11 years, 11 months ago) by grimes
Branch: MAIN
Changes since 1.7: +146 -18 lines
Log Message:
Modified the file format to get around a protobuf aversion to extremely long messages. Also changed output to be gzipped.

File Contents

# Content
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 {
19 class ReducedEventImplementation : public l1menu::IReducedEvent
20 {
21 public:
22 virtual ~ReducedEventImplementation() {}
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
43 namespace l1menu
44 {
45 /** @brief Private members for the ReducedMenuSample class
46 *
47 * @author Mark Grimes (mark.grimes@bristol.ac.uk)
48 * @date 28/May/2013
49 */
50 class ReducedMenuSamplePrivateMembers
51 {
52 private:
53 l1menu::TriggerMenu mutableTriggerMenu_;
54 public:
55 ReducedMenuSamplePrivateMembers( const l1menu::TriggerMenu& newTriggerMenu );
56 ReducedMenuSamplePrivateMembers( const std::string& filename );
57 void copyMenuToProtobufSample();
58 ::ReducedEventImplementation event;
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::ReducedMenuSamplePrivateMembers::ReducedMenuSamplePrivateMembers( const l1menu::TriggerMenu& newTriggerMenu )
74 : mutableTriggerMenu_( newTriggerMenu ), triggerMenu( mutableTriggerMenu_ )
75 {
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
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
295 }
296
297 size_t l1menu::ReducedMenuSample::numberOfEvents() const
298 {
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 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 }