00001
00009 #include "FluxSvc/IRegisterSource.h"
00010
00011 #include "facilities/Timestamp.h"
00012 #include "facilities/Observer.h"
00013 #include "facilities/Util.h"
00014
00015 #include "celestialSources/SpectrumFactoryLoader.h"
00016
00017
00018 #include "GaudiKernel/SvcFactory.h"
00019 #include "GaudiKernel/MsgStream.h"
00020 #include "GaudiKernel/GaudiException.h"
00021 #include "GaudiKernel/IObjManager.h"
00022 #include "GaudiKernel/IToolFactory.h"
00023 #include "GaudiKernel/IAlgManager.h"
00024 #include "GaudiKernel/Algorithm.h"
00025 #include "GaudiKernel/IAppMgrUI.h"
00026 #include "GaudiKernel/IParticlePropertySvc.h"
00027
00028 #include "CLHEP/Random/Random.h"
00029
00030 #include "astro/SkyDir.h"
00031 #include "astro/EarthOrbit.h"
00032 #include "astro/EarthCoordinate.h"
00033
00034 #include "flux/Flux.h"
00035 #include "flux/FluxMgr.h"
00036 #include "flux/rootplot.h"
00037 #include "flux/ISpectrumFactory.h"
00038 #include "flux/Spectrum.h"
00039
00040 #include <cassert>
00041 #include <algorithm>
00042 #include <iterator>
00043 #include <sstream>
00044 #include <iomanip>
00045 #include <cstdlib>
00046
00047 using astro::GPS;
00058
00059 #include "GaudiKernel/Service.h"
00060 #include "GaudiKernel/IRunable.h"
00061 #include "GaudiKernel/Property.h"
00062
00063 #include "FluxSvc/IFluxSvc.h"
00064 #include <list>
00065
00066
00067 template <class TYPE> class SvcFactory;
00068 class IFlux;
00069 class FluxMgr;
00070 class IParticlePropertySvc;
00071 class IAppMgrUI;
00072
00073 class FluxSvc :
00074 virtual public Service,
00075 virtual public IFluxSvc,
00076 virtual public IRunable
00077 {
00078 public:
00079
00081 StatusCode source(std::string name, IFlux*&);
00082
00084 StatusCode compositeSource(std::vector<std::string> names, IFlux*& flux);
00085
00087 std::list<std::string> fluxNames()const;
00088
00090 virtual void addFactory(std::string name, const ISpectrumFactory* factory );
00091
00093 virtual void pass ( double t);
00094
00095
00097 virtual CLHEP::HepRandomEngine* getRandomEngine();
00098 virtual void rootDisplay(std::vector<std::string> arguments);;
00099
00101 void attachGpsObserver(Observer* anObserver);
00102
00104 IFlux* currentFlux();
00105
00107 std::string fluxName()const;
00108
00110 void setPointingDirection(const astro::SkyDir& dir);
00111
00113 std::pair<double,double> getExplicitRockingAngles();
00114
00116 CLHEP::HepRotation transformGlastToGalactic(double time)const;
00117
00118 CLHEP::HepRotation transformToGlast(double seconds,GPS::CoordSystem index)const;
00119
00121 std::pair<double,double> location();
00122
00124 GPS* GPSinstance();
00125
00127 std::string uniqueIDString()const;
00128
00136 std::vector<double> setRockType(astro::GPS::RockType rockType, double rockAngle = 35.);
00137
00139 std::vector<std::pair< std::string ,std::list<std::string> > > sourceOriginList() const;
00140
00141
00142 bool insideSAA() { return m_insideSAA;}
00143
00144
00146 virtual StatusCode run();
00147
00148 double endruntime();
00149
00150 virtual void setFilterCone(std::vector<double> cone);
00151
00155 virtual void setAlignmentRotation(double qx, double qy, double qz, bool misalign);
00156
00159 virtual void setSAABoundary(const std::vector<std::pair<double, double> > & boundary);
00160
00161
00162
00163
00164
00166 virtual StatusCode initialize ();
00167
00169 virtual StatusCode finalize ();
00170
00172 virtual StatusCode queryInterface( const InterfaceID& riid, void** ppvUnknown );
00173
00174 protected:
00175
00177 FluxSvc ( const std::string& name, ISvcLocator* al );
00178
00180 virtual ~FluxSvc ();
00181
00182 private:
00184
00185 IParticlePropertySvc* m_partSvc;
00186
00188 friend class SvcFactory<FluxSvc>;
00189
00190 FluxMgr * m_fluxMgr;
00192
00193 std::vector<std::string> m_source_lib;
00195 std::string m_source_lib_default;
00197 std::string m_dtd_file;
00199 IFlux* m_currentFlux;
00200
00201
00203 IAppMgrUI* m_appMgrUI;
00204 IntegerProperty m_evtMax;
00205
00206
00210 class Times {
00211 public:
00212 void initialize(MsgStream& log){
00213
00214
00215 m_start = convert(m_startDate);
00216 m_launch= convert(m_launchDate);
00217 if( m_launch==0 ) m_launch=m_start;
00218
00219
00220 double offset = m_startTime.value();
00221 double delta = m_deltaTime.value();
00222 if(! m_startTimeEnvVar.value().empty()) {
00223 std::string t(m_startTimeEnvVar.value());
00224 if( t.substr(0,2)=="$(" && t.size()>3 ) {
00225 t= t.substr(2, t.size()-3);
00226 }
00227 const char* envar=::getenv(t.c_str());
00228 if( envar==0 ){
00229 log << MSG::WARNING << "Env var \"" << t
00230 << "\" requested for start time, not found, using value of StartTime: "
00231 << m_startTime << endreq;
00232 } else {
00233
00234 std::string ev(envar);
00235 int comma = ev.find(',');
00236 offset = ::atof( ev.substr(0,comma>0?comma+1:-1).c_str() );
00237 log << MSG::INFO << "Setting start time offset from environment variable "
00238 << t << " to " << offset;
00239 if( comma>0){
00240 delta = ::atof(ev.substr(comma+1).c_str());
00241 log << "\n\t\t\tsetting deltat to " << delta;
00242 }
00243 log << endreq;
00244 }
00245 }
00246 m_start += offset;
00247
00248
00249 if( delta >0 && m_endTime==0 ) m_endTime=m_start+delta;
00250
00251 GPS::instance()->time(m_start);
00252 m_end = m_endTime;
00253 if( m_deltaTime>0) m_end=m_start+delta;
00254
00255 log << MSG::INFO << "init: start time = " << std::setprecision(12)
00256 << m_start << " sec, end time = " << m_end << " sec, delta = "
00257 << m_end-m_start << " sec" << endreq;
00258
00259 }
00260 double convert(std::string date){
00261 using astro::JulianDate;
00262 if(date.empty())return 0;
00263
00264 facilities::Timestamp jt(date);
00265 return (JulianDate(jt.getJulian())-JulianDate::missionStart())*JulianDate::secondsPerDay;
00266 }
00267
00268 StringProperty m_launchDate;
00269 StringProperty m_startDate;
00270 DoubleProperty m_startTime;
00271 DoubleProperty m_endTime;
00272 DoubleProperty m_deltaTime;
00273 StringProperty m_startTimeEnvVar;
00274
00275 double m_launch;
00276 double m_start;
00277 double m_end;
00278 double m_current;
00279 double launch(){return m_launch;}
00280 double start(){return m_start;}
00281 double current(){ return GPS::instance()->time();}
00282 double end(){return m_end;}
00283 double offset(double t){return t-m_launch;}
00284 } m_times;
00285
00286
00287 ObserverAdapter< FluxSvc > m_observer;
00288 int askGPS();
00289 bool m_insideSAA;
00290
00291 DoubleProperty m_expansionFactor;
00292 DoubleProperty m_sampleInterval;
00293 DoubleProperty m_orbitInclination;
00294
00295 DoubleArrayProperty m_SAA_poly_lat;
00296 DoubleArrayProperty m_SAA_poly_lon;
00297 StringProperty m_xmlFiles;
00298 BooleanProperty m_aberrate;
00299
00300
00301 };
00302
00303
00304 static SvcFactory<FluxSvc> a_factory;
00305 const ISvcFactory& FluxSvcFactory = a_factory;
00306
00307
00308 static std::string default_source_library("$(FLUXXMLPATH)/source_library.xml");
00309 static std::string default_dtd_file("$(FLUXXMLPATH)/source.dtd");
00310
00311
00312
00313
00315 FluxSvc::FluxSvc(const std::string& name,ISvcLocator* svc)
00316 : Service(name,svc), m_currentFlux(0), m_insideSAA(false)
00317 {
00318
00319 declareProperty("source_lib" , m_source_lib);
00320 declareProperty("dtd_file" , m_dtd_file=default_dtd_file);
00321 declareProperty("EvtMax" , m_evtMax=0);
00322
00323 declareProperty("StartTime" , m_times.m_startTime=0);
00324 declareProperty("EndTime" , m_times.m_endTime=0);
00325 declareProperty("DeltaTime" , m_times.m_deltaTime=0);
00326 declareProperty("StartDate" , m_times.m_startDate="");
00327 declareProperty("LaunchDate" , m_times.m_launchDate="");
00328 declareProperty("StartTimeEnvVar", m_times.m_startTimeEnvVar="");
00329 declareProperty("SampleInterval", m_sampleInterval=1.0);
00330 declareProperty("OrbitInclination", m_orbitInclination=25.3);
00331
00332 declareProperty("SAApolyLat" , m_SAA_poly_lat);
00333 declareProperty("SAApolyLon" , m_SAA_poly_lon);
00334 declareProperty("xmlListFile" , m_xmlFiles="");
00335 declareProperty("EnableAberration", m_aberrate=false);
00336
00337 }
00338
00339 std::list<std::string> FluxSvc::fluxNames()const{
00340 return m_fluxMgr->sourceList();
00341 }
00342
00343 StatusCode FluxSvc::source(std::string name, IFlux*& flux) {
00344 std::list<std::string> source_list( fluxNames() );
00345 std::list<std::string> source_list2( SpectrumFactoryTable::instance()->spectrumList() );
00346
00347 if( std::find(source_list.begin(), source_list.end(), name) == source_list.end()
00348 &&(std::find(source_list2.begin(), source_list2.end(), name) == source_list2.end()))
00349 return StatusCode::FAILURE;
00350 flux = new Flux(name);
00351 m_currentFlux = flux;
00352 return StatusCode::SUCCESS;
00353 }
00354
00355 StatusCode FluxSvc::compositeSource(std::vector<std::string> names, IFlux*& flux) {
00356 flux = new Flux(names);
00357 if( flux->currentEvent()==0) return StatusCode::FAILURE;
00358 m_currentFlux = flux;
00359 return StatusCode::SUCCESS;
00360 }
00362 FluxSvc::~FluxSvc()
00363 {
00364 }
00365
00366 StatusCode FluxSvc::initialize ()
00367 {
00368 StatusCode status = Service::initialize ();
00369
00370
00371 setProperties ();
00372
00373
00374
00375 MsgStream log( msgSvc(), name() );
00376
00377
00378 astro::EarthOrbit::set_inclination(m_orbitInclination);
00379
00380
00381 m_times.initialize(log);
00382
00383
00384 Spectrum::setStartTime(m_times.launch());
00385
00386
00387 status = serviceLocator()->queryInterface(IID_IAppMgrUI, (void**)&m_appMgrUI);
00388
00389
00390 if( !m_xmlFiles.value().empty() ) {
00391
00392 std::string xmlFiles(m_xmlFiles.value() );
00393 facilities::Util::expandEnvVar(&xmlFiles);
00394 std::ifstream xmls(xmlFiles.c_str());
00395 if( !xmls.is_open() ){
00396 throw std::invalid_argument("File not found: " + xmlFiles);
00397 }
00398 while( ! xmls.eof()){
00399 std::string line; std::getline(xmls, line);
00400 if( line.empty() || line[0]=='#' ) continue;
00401 m_source_lib.push_back(line);
00402 }
00403 }
00404
00405
00406 if( m_source_lib.empty() ){
00407 m_source_lib.push_back(default_source_library);
00408 log << MSG::INFO << "Set source library list to " << default_source_library << endreq;
00409 }
00410
00411 try {
00412
00413 m_fluxMgr = new FluxMgr(m_source_lib, m_dtd_file);
00414 }catch(...){
00415 return StatusCode::FAILURE;
00416 }
00417
00418 Flux::mgr(m_fluxMgr);
00419
00420
00421 if( m_fluxMgr->sourceList().empty()) {
00422 log << MSG::ERROR << "Did not initialize properly: no sources detected" << endreq;
00423 status = StatusCode::FAILURE;
00424 }
00425
00426 m_fluxMgr->setExpansion(m_expansionFactor);
00427
00428 if ( service("ParticlePropertySvc", m_partSvc).isFailure() ){
00429 log << MSG::ERROR << "Couldn't find the ParticlePropertySvc!" << endreq;
00430 return StatusCode::FAILURE;
00431 }
00432
00433 log << MSG::INFO << "Registering factories external to flux: ";
00434 SpectrumFactoryLoader externals;
00435 std::vector<std::string> flux_names(externals.names());
00436
00437 std::copy( flux_names.begin(), flux_names.end(),
00438 std::ostream_iterator<std::string>(log.stream(), ", "));
00439 log << endreq;
00440
00441 size_t nsaa =m_SAA_poly_lat.value().size();
00442 if( nsaa>0) {
00443 if( m_SAA_poly_lon.value().size() != nsaa ){
00444 log << MSG::ERROR <<"sizes of SAA arrays do not match"<< endreq;
00445 return StatusCode::FAILURE;
00446 }
00447 std::vector<std::pair<double,double> > saa_array;
00448 for( size_t i = 0; i< nsaa; ++i){
00449 saa_array.push_back( std::make_pair(m_SAA_poly_lat.value()[i], m_SAA_poly_lon.value()[i]) );
00450 }
00451 astro::EarthCoordinate::setSAAboundary( saa_array);
00452 }
00453
00454
00455 if( m_aberrate ){
00456 log << MSG::INFO << "Enabled generation of stellar aberration" << endreq;
00457 }
00458 astro::GPS::instance()->enableAberration(m_aberrate.value());
00459
00460
00461
00462
00463
00464
00465
00466
00467
00468 IObjManager* objManager=0;
00469
00470
00471 status = serviceLocator()->service("ApplicationMgr", objManager );
00472 if( status.isFailure()) {
00473 log << MSG::ERROR << "Unable to locate ObjectManager Service" << endreq;
00474 return status;
00475 }
00476
00477 IToolFactory* toolfactory = 0;
00478
00479
00480 for(IObjManager::ObjIterator it = objManager->objBegin(); it !=objManager->objEnd(); ++ it){
00481
00482 std::string tooltype= (*it)->ident();
00483
00484 const IFactory* factory = objManager->objFactory( tooltype );
00485 IFactory* fact = const_cast<IFactory*>(factory);
00486 status = fact->queryInterface( IID_IToolFactory, (void**)&toolfactory );
00487 if( status.isSuccess() ) {
00488
00489 IAlgTool* itool = toolfactory->instantiate(name()+"."+tooltype, this );
00490 IRegisterSource* ireg;
00491 status =itool->queryInterface( IRegisterSource::interfaceID(), (void**)&ireg);
00492 if( status.isSuccess() ){
00493 log << MSG::INFO << "Registering sources in " << tooltype << endreq;
00494 ireg->registerMe(this);
00495 }
00496 log << MSG::DEBUG << "Releasing the tool " << tooltype << endreq;
00497 itool->release();
00498 }
00499
00500 }
00501
00502
00503
00504 m_observer.setAdapter( new ActionAdapter<FluxSvc>
00505 (this, &FluxSvc::askGPS) );
00506
00507 attachGpsObserver(&m_observer);
00508
00509 astro::GPS::instance()->sampleintvl(m_sampleInterval.value());
00510 return StatusCode::SUCCESS;
00511 }
00512
00513 int FluxSvc::askGPS()
00514 {
00515 astro::EarthCoordinate pos = GPS::instance()->earthpos();
00516 bool inside = pos.insideSAA();
00517
00518 double curtime = GPS::instance()->time();
00519
00520 if( m_insideSAA == inside) return 0;
00521
00522 MsgStream log( msgSvc(), name() );
00523 log << MSG::INFO;
00524 std::stringstream t;
00525 t<< (!inside? " leaving" : "entering")
00526 << " SAA at " << std::setprecision(10)<< curtime;
00527 log << t.str() << endreq;
00528 m_insideSAA = inside;
00529
00530 return 0;
00531 }
00532
00533
00535 CLHEP::HepRandomEngine* FluxSvc::getRandomEngine(){
00536 return CLHEP::HepRandom::getTheEngine();
00537 };
00538
00539
00540 StatusCode FluxSvc::finalize ()
00541 {
00542 StatusCode status = StatusCode::SUCCESS;
00543
00544 delete m_fluxMgr;
00545 return status;
00546 }
00547
00549 StatusCode FluxSvc::queryInterface(const InterfaceID& riid, void** ppvInterface) {
00550 if ( IID_IFluxSvc.versionMatch(riid) ) {
00551 *ppvInterface = (IFluxSvc*)this;
00552 }else if (IID_IRunable.versionMatch(riid) ) {
00553 *ppvInterface = (IRunable*)this;
00554 } else {
00555 return Service::queryInterface(riid, ppvInterface);
00556 }
00557
00558 addRef();
00559 return SUCCESS;
00560 }
00561
00562
00564 void FluxSvc::addFactory(std::string name, const ISpectrumFactory* factory ){
00565 m_fluxMgr->addFactory(name, factory);
00566 }
00567
00568
00570 void FluxSvc::pass ( double t){
00571 m_fluxMgr->pass(t);
00572 }
00573
00574 void FluxSvc::rootDisplay(std::vector<std::string> arguments){
00575 rootplot abc(arguments, m_fluxMgr);
00576 }
00577
00578
00579 void FluxSvc::attachGpsObserver(Observer* anObserver)
00580 {
00581 GPS::instance()->notification().attach( anObserver );
00582 GPS::instance()->notifyObservers();
00583 }
00584
00585
00587 GPS* FluxSvc::GPSinstance(){ return GPS::instance();}
00588
00589
00591 IFlux* FluxSvc::currentFlux(){
00592 return m_currentFlux;
00593 }
00594
00596 std::string FluxSvc::fluxName()const{
00597 return m_currentFlux->name();
00598 }
00599
00601 std::string FluxSvc::uniqueIDString()const{
00602 std::stringstream t;
00603 t << m_currentFlux->numSource();
00604 return m_currentFlux->name() + t.str();
00605 }
00606
00607
00608 void FluxSvc::setPointingDirection(const astro::SkyDir& dir){
00609 astro::GPS::instance()->setPointingDirection(dir);
00610 }
00611
00612
00614
00615
00616 CLHEP::HepRotation FluxSvc::transformToGlast(double time, GPS::CoordSystem index)const{
00617 return m_fluxMgr->transformToGlast(time,index);
00618 }
00619
00620 CLHEP::HepRotation FluxSvc::transformGlastToGalactic(double time)const{
00621 return transformToGlast(time, GPS::CELESTIAL).inverse();
00622 }
00623
00624 std::pair<double,double> FluxSvc::location(){
00625 return m_fluxMgr->location();
00626 }
00628 std::vector<double> FluxSvc::setRockType(astro::GPS::RockType rockType, double rockAngle){
00629 return m_fluxMgr->setRockType(rockType, rockAngle);
00630 }
00631
00632 std::vector<std::pair< std::string ,std::list<std::string> > > FluxSvc::sourceOriginList() const{
00633 return m_fluxMgr->sourceOriginList();
00634 }
00635
00636 double FluxSvc::endruntime() {
00637 return m_times.end() ;
00638 }
00639
00640
00641 StatusCode FluxSvc::run(){
00642 StatusCode status = StatusCode::FAILURE;
00643 MsgStream log( msgSvc(), name() );
00644
00645 if ( 0 == m_appMgrUI ) return status;
00646
00647 IProperty* propMgr=0;
00648 status = serviceLocator()->service("ApplicationMgr", propMgr );
00649 if( status.isFailure()) {
00650 log << MSG::ERROR << "Unable to locate PropertyManager Service" << endreq;
00651 return status;
00652 }
00653
00654 IntegerProperty evtMax("EvtMax",0);
00655 status = propMgr->getProperty( &evtMax );
00656 if (status.isFailure()) return status;
00657
00658 setProperty(evtMax);
00659
00660
00661
00662 IAlgManager* theAlgMgr;
00663 status = serviceLocator( )->getService( "ApplicationMgr",
00664 IID_IAlgManager,
00665 (IInterface*&)theAlgMgr );
00666 IAlgorithm* theIAlg;
00667 Algorithm* theAlgorithm=0;
00668 IntegerProperty errorProperty("ErrorCount",0);
00669
00670 status = theAlgMgr->getAlgorithm( "Top", theIAlg );
00671 if ( status.isSuccess( ) ) {
00672 try{
00673 theAlgorithm = dynamic_cast<Algorithm*>(theIAlg);
00674 } catch(...){
00675 status = StatusCode::FAILURE;
00676 }
00677 }
00678 if ( status.isFailure( ) ) {
00679 log << MSG::WARNING << "Could not find algorithm 'Top'; will not monitor errors" << endreq;
00680 }
00681
00682
00683
00684 int eventNumber= 0;
00685
00686
00687 { bool noend=true;
00688 log << MSG::INFO << "Runable interface starting event loop as :" ;
00689 if( m_evtMax>0) { log << " MaxEvt = " << m_evtMax; noend=false; }
00690 if( m_times.start()>0) { log << " StartTime= launch+" << m_times.start() -m_times.launch(); }
00691 if( m_times.end()>0 ) { log << " EndTime=launch+ " << m_times.end()-m_times.launch(); noend=false; }
00692 log << endreq;
00693
00694 if(noend) {
00695 log << MSG::ERROR<< "No end condition specified: will not process any events!" << endreq;
00696 return StatusCode::FAILURE;
00697 }
00698 }
00699 if( m_times.end()>0 && m_times.start() > m_times.end()){
00700 log << MSG::ERROR << "Start time after end time!" << endreq;
00701 return StatusCode::FAILURE;
00702 }
00703 int last_fraction=0;
00704
00705 GPS::instance()->notifyObservers();
00706
00707
00708 bool first(true);
00709 while( (m_evtMax==0 || m_evtMax>0 && eventNumber < m_evtMax)
00710 && (m_times.end()==0 || m_times.end()>0 && m_times.current() < m_times.end()) ) {
00711
00712 double efrac = (m_evtMax>0? 100.*eventNumber/m_evtMax: 0.0),
00713 tfrac = (m_times.end()>0? 100.*(m_times.current()-m_times.start())/(m_times.end()-m_times.start()) : 0.0) ;
00714
00715 int percent_complete= static_cast<int>( std::max( efrac, tfrac) );
00716 if( percent_complete!=last_fraction){
00717 last_fraction=percent_complete;
00718 if( percent_complete<10 || percent_complete%10 ==0 || first){
00719 first = false;
00720
00722 facilities::Timestamp tstamp;
00723
00724 log << MSG::INFO
00725 << " [" << tstamp.getString() << "] "
00726 << std::setprecision(12)<< std::resetiosflags(4096)
00727 << percent_complete << "% complete: "
00728 << " event "<< eventNumber<<", time= "
00729 << m_times.current() << "= launch+ "
00730 << (m_times.current()-m_times.launch()) << endreq;
00731 }
00732
00733 }
00734
00735 status = m_appMgrUI->nextEvent(1);
00736
00737
00738 if( theAlgorithm !=0) theAlgorithm->getProperty(&errorProperty);
00739 if( status.isFailure() || errorProperty.value() > 0){
00740 status = StatusCode::FAILURE;
00741 }
00742
00743 if( status.isFailure()) break;
00744 eventNumber ++;
00745 }
00746 if( status.isFailure()){
00747 log << MSG::ERROR << "Terminating FluxSvc loop due to error" << endreq;
00748
00749 }else if( m_times.end()>0 && m_times.current() >= m_times.end() ) {
00750 log << MSG::INFO << "Loop terminated by time " << endreq;
00751 }else {
00752 log << MSG::INFO << "Processing loop terminated by event count" << endreq;
00753 }
00754 log << MSG::INFO << "End after "<< eventNumber << " events, time = " << m_times.current() << endreq;
00755 return status;
00756 }
00757
00758 void FluxSvc::setAlignmentRotation(double qx, double qy, double qz, bool misalign)
00759 {
00760 m_fluxMgr->setAlignmentRotation(qx, qy, qz, misalign);
00761 }
00762
00763
00764 void FluxSvc::setFilterCone(std::vector<double> cone)
00765 {
00766 assert( cone.size()>2);
00767 m_fluxMgr->setFilterCone(cone[0], cone[1], cone[2]);
00768 }
00769
00770
00771
00772 void FluxSvc::setSAABoundary(const std::vector<std::pair<double, double> > & boundary)
00773 {
00774 }
00775