Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-09 07:49:47

0001 #pragma once
0002 /**
0003 sstandard.h : standard domain arrays
0004 ========================================
0005 
0006 This houses standardized "mat" and "sur" arrays with
0007 material/surface properties interpolated onto a standard
0008 energy/wavelength domain.
0009 
0010 mat
0011     populated by U4Tree::initMaterials using U4Material::MakeStandardArray
0012     aiming to replace X4/GGeo workflow GMaterialLib buffer.
0013     The shape of the mat array::
0014 
0015       (~20:num_mat, 2:num_payload_cat, num_wavelength_samples, 4:payload_values )
0016 
0017 sur
0018     populated by U4Tree::initSurfaces_Serialize using U4SurfaceArray.h
0019     aiming to replace X4/GGeo workflow GSurfaceLib buffer
0020 
0021 bnd
0022     interleaved mat and sur array items
0023     Example shapes::
0024 
0025         mat    (20, 2, 761, 4)
0026         sur    (40, 2, 761, 4)
0027         bnd (53, 4, 2, 761, 4)
0028 
0029     The (2,761,4) is considered the payload comprising
0030     two payload groups and payload quad at 761 interpolated
0031     domain energies/wavelenths.
0032     This payload shape is used to allow the bnd values
0033     to be accessed via a float4 GPU texture.
0034 
0035 bd
0036    (omat,osur,isur,imat) int pointers
0037 
0038 optical
0039    material and surface param
0040 
0041 wavelength
0042    from sdomain::get_wavelength_nm
0043 
0044 energy
0045    from sdomain::get_energy_eV
0046 
0047 rayleigh
0048    populated by U4Tree::initRayleigh
0049 
0050 
0051 
0052 
0053 In the old X4/GGeo workflow, the bnd buffer was created with::
0054 
0055     GBndLib::createBufferForTex2d
0056     -------------------------------
0057 
0058     GBndLib double buffer is a memcpy zip of the MaterialLib and SurfaceLib buffers
0059     pulling together data based on the indices for the materials and surfaces
0060     from the m_bnd guint4 buffer
0061 
0062     Typical dimensions : (128, 4, 2, 39, 4)
0063 
0064                128 : boundaries,
0065                  4 : mat-or-sur for each boundary
0066                  2 : payload-categories corresponding to NUM_FLOAT4
0067                 39 : wavelength samples
0068                  4 : double4-values
0069 
0070     The only dimension that can easily be extended is the middle payload-categories one,
0071     the low side is constrained by layout needed to OptiX tex2d<float4> as this
0072     buffer is memcpy into the texture buffer
0073     high side is constained by not wanting to change texture line indices
0074 
0075     The 39 wavelength samples is historical. There is a way to increase this
0076     to 1nm FINE_DOMAIN binning.
0077 
0078 **/
0079 
0080 #include <limits>
0081 #include <array>
0082 #include <csignal>
0083 
0084 #include "NPFold.h"
0085 #include "NPX.h"
0086 #include "sproplist.h"
0087 #include "sdomain.h"
0088 #include "smatsur.h"
0089 #include "snam.h"
0090 
0091 struct sstandard
0092 {
0093     static constexpr const bool VERBOSE = false ;
0094     static constexpr const char* IMPLICIT_PREFIX = "Implicit_RINDEX_NoRINDEX" ;
0095     const sdomain* dom ;
0096 
0097     const NP* wavelength ;
0098     const NP* energy ;
0099     const NP* rayleigh ;
0100     const NP* mat ;
0101     const NP* sur ;
0102     const NP* bd ;
0103     const NP* bnd ;
0104     const NP* optical ;
0105 
0106     const NP* icdf ;
0107 
0108 
0109     sstandard();
0110 
0111     void deferred_init(
0112         const std::vector<int4>& vbd,
0113         const std::vector<std::string>& bdname,
0114         const std::vector<std::string>& suname,
0115         const NPFold* surface
0116     );
0117 
0118     NPFold* serialize() const ;
0119     void import(const NPFold* fold );
0120 
0121     void save(const char* base, const char* rel );
0122     void load(const char* base, const char* rel );
0123 
0124 
0125     static NP* make_bd(
0126         const std::vector<int4>& vbd,
0127         const std::vector<std::string>& bdname
0128     );
0129 
0130     static NP* make_optical(
0131         const std::vector<int4>& vbd,
0132         const std::vector<std::string>& suname,
0133         const NPFold* surface
0134     );
0135 
0136     static NP* make_bnd(
0137         const std::vector<int4>& vbd,
0138         const std::vector<std::string>& bdname,
0139         const NP* mat,
0140         const NP* sur
0141     );
0142 
0143     static void column_range(int4& mn, int4& mx,  const std::vector<int4>& vbd) ;
0144     static NP* unused_mat(const std::vector<std::string>& names, const NPFold* fold );
0145     static NP* unused_sur(const std::vector<std::string>& names, const NPFold* fold );
0146     static NP* unused_create(const sproplist* pl,  const std::vector<std::string>& names, const NPFold* fold );
0147 };
0148 
0149 
0150 inline sstandard::sstandard()
0151     :
0152     dom(nullptr),
0153     wavelength(nullptr),
0154     energy(nullptr),
0155     rayleigh(nullptr),
0156     mat(nullptr),
0157     sur(nullptr),
0158     bd(nullptr),
0159     bnd(nullptr),
0160     optical(nullptr),
0161     icdf(nullptr)
0162 {
0163 }
0164 
0165 
0166 /**
0167 sstandard::deferred_init
0168 --------------------------
0169 
0170 NB deferred init called from stree::initStandard
0171 after mat and sur have been filled.
0172 
0173 * standard->mat is populated by U4Tree::initMaterials
0174   using U4Material::MakeStandardArray
0175 
0176 
0177 **/
0178 
0179 inline void sstandard::deferred_init(
0180         const std::vector<int4>& vbd,
0181         const std::vector<std::string>& bdname,
0182         const std::vector<std::string>& suname,
0183         const NPFold* surface
0184     )
0185 {
0186     dom = new sdomain ;
0187 
0188     wavelength = dom->get_wavelength_nm() ;
0189     energy = dom->get_energy_eV() ;
0190 
0191     bd      = make_bd(     vbd, bdname );
0192     bnd     = make_bnd(    vbd, bdname, mat, sur ) ;
0193     optical = make_optical(vbd, suname, surface) ;
0194 }
0195 
0196 
0197 inline NPFold* sstandard::serialize() const
0198 {
0199     NPFold* fold = new NPFold ;
0200 
0201     fold->add(snam::WAVELENGTH , wavelength );
0202     fold->add(snam::ENERGY,      energy );
0203 
0204     fold->add(snam::RAYLEIGH,    rayleigh );
0205     fold->add(snam::MAT ,    mat );
0206     fold->add(snam::SUR ,    sur );
0207 
0208     fold->add(snam::BD,      bd );
0209     fold->add(snam::BND,     bnd );
0210     fold->add(snam::OPTICAL, optical );
0211 
0212     fold->add(snam::ICDF, icdf) ;
0213 
0214     return fold ;
0215 }
0216 
0217 inline void sstandard::import(const NPFold* fold )
0218 {
0219     wavelength = fold->get(snam::WAVELENGTH);
0220     energy = fold->get(snam::ENERGY);
0221 
0222     rayleigh = fold->get(snam::RAYLEIGH);
0223     mat = fold->get(snam::MAT);
0224     sur = fold->get(snam::SUR);
0225 
0226     bd = fold->get(snam::BD);
0227     bnd = fold->get(snam::BND);
0228     optical = fold->get(snam::OPTICAL);
0229 
0230     icdf = fold->get(snam::ICDF);
0231 }
0232 
0233 inline void sstandard::save(const char* base, const char* rel )
0234 {
0235     NPFold* fold = serialize();
0236     fold->save(base, rel);
0237 }
0238 
0239 inline void sstandard::load(const char* base, const char* rel )
0240 {
0241     NPFold* fold = NPFold::Load(base, rel) ;
0242     import(fold) ;
0243 }
0244 
0245 
0246 /**
0247 sstandard::make_bd
0248 -------------------
0249 
0250 Create array of shape (num_bd, 4) holding int "pointers" to (omat,osur,isur,imat)
0251 
0252 **/
0253 
0254 inline NP* sstandard::make_bd( const std::vector<int4>& vbd, const std::vector<std::string>& bdname )
0255 {
0256     NP* a_bd = NPX::ArrayFromVec<int, int4>( vbd );
0257     a_bd->set_names( bdname );
0258     return a_bd ;
0259 }
0260 
0261 /**
0262 sstandard::make_optical
0263 -------------------------
0264 
0265 The optical buffer int4 payload has entries for both materials and surfaces.
0266 Array shape at creation and as persisted::
0267 
0268                   int4 payload
0269                   |
0270      (num_bnd, 4, 4)
0271                |
0272              (omat,osur,isur,imat)
0273 
0274 Shape at point of use on GPU combines the first two dimensions to give "line"
0275 access to materials and surfaces::
0276 
0277      (num_bnd*4, 4 )
0278 
0279 The optical buffer int4 payloads for materials and surfaces:
0280 
0281 +------------------+---------------+-------------------------------+--------------------------+-------------------+
0282 |                  | .x            |  .y   Payload_Y               |  .z                      |  .w               |
0283 +==================+===============+===============================+==========================+===================+
0284 | MATERIAL LINES   |   idx+1       |  UNUSED                       | UNUSED                   |  UNUSED           |
0285 +------------------+---------------+-------------------------------+--------------------------+-------------------+
0286 | SURFACE LINES    |               | type                          | finish                   |  value_percent    |
0287 | [FORMERLY]       |   idx+1       | 0:dielectric_metal            | 0:polished               |                   |
0288 |                  |               | 1:dielectric_dielectric       | 1:polishedfrontpainted   |                   |
0289 |                  |               |                               | 3:ground                 |                   |
0290 |                  |               +-------------------------------+--------------------------+-------------------+
0291 |                  |               |  YZW : THESE THREE COLUMNS WERE FORMERLY NEVER USED ON DEVICE                |
0292 +------------------+---------------+-------------------------------+--------------------------+-------------------+
0293 | SURFACE LINES    |   idx+1       | smatsur::TypeFromChar(OSN0)   | ZW : AS ABOVE BUT STILL NOT USED ON DEVICE   |
0294 | [NOW]            |               | [MAT/SUR TYPE ENUM "ems"]     |                                              |
0295 +------------------+---------------+-------------------------------+----------------------------------------------+
0296 
0297 Q: How come the yzw columns not used on device ?
0298 A: Because that info is used on CPU to prepare the surface entries
0299    of the bnd array, which are accessed on device via the boundary texture.
0300 
0301 
0302 HANDLING SIGMA_ALPHA/POLISH GROUND SURFACES ?
0303 -----------------------------------------------
0304 
0305 This loops over all surfaces in use in the geometry, so
0306 can detect surfaces that need special handling : and communicate
0307 that via the ems smatsur.h enum value.
0308 
0309 **/
0310 
0311 inline NP* sstandard::make_optical(
0312      const std::vector<int4>& vbd,
0313      const std::vector<std::string>& suname,
0314      const NPFold* surface )
0315 {
0316     int ni = vbd.size() ;
0317     int nj = 4 ;
0318     int nk = 4 ;
0319 
0320     NP* op = NP::Make<int>(ni, nj, nk);
0321     int* op_v = op->values<int>();
0322 
0323     for(int i=0 ; i < ni ; i++)       // over vbd
0324     {
0325         const int4& bd_ = vbd[i] ;
0326         for(int j=0 ; j < nj ; j++)   // over (omat,osur,isur,imat)
0327         {
0328             int op_index = i*nj*nk + j*nk ;
0329 
0330             int idx = -2 ;
0331             switch(j)
0332             {
0333                 case 0: idx = bd_.x ; break ;
0334                 case 1: idx = bd_.y ; break ;
0335                 case 2: idx = bd_.z ; break ;
0336                 case 3: idx = bd_.w ; break ;
0337             }
0338             int idx1 = idx+1 ;  // 1-based idx
0339             bool is_mat = j == 0 || j == 3 ;
0340             bool is_sur = j == 1 || j == 2 ;
0341 
0342             if(is_mat)
0343             {
0344                 assert( idx > -1 );   // omat,imat must always be present
0345                 op_v[op_index+0] = idx1 ;
0346                 op_v[op_index+1] = 0 ;
0347                 op_v[op_index+2] = 0 ;
0348                 op_v[op_index+3] = 0 ;
0349             }
0350             else if(is_sur)
0351             {
0352                 const char* surfname = snam::get(suname, idx) ;
0353 
0354                 bool no_surfname_for_surface_idx = idx > -1 && surfname == nullptr ;
0355 
0356                 if(no_surfname_for_surface_idx) std::cerr
0357                     << "sstandard::make_optical"
0358                     << " ERROR "
0359                     << " no_surfname_for_surface_idx " << ( no_surfname_for_surface_idx ? "YES" : "NO " )
0360                     << " sur idx from bd " << idx
0361                     << " but no corresponding surfname "
0362                     << " suname.size " << suname.size()
0363                     << " surface.subfold.size " << surface->subfold.size()
0364                     << " surface.ff.size " << surface->ff.size()
0365                     << "\n"
0366                     << " snam::Desc(suname)\n"
0367                     << snam::Desc(suname)
0368                     << "\n"
0369                     ;
0370 
0371                 if(idx > -1 ) assert(surfname) ;
0372                 // all surf should have name, do not always have surf
0373 
0374                 NPFold* surf = surfname ? surface->get_subfold(surfname) : nullptr ;
0375                 bool is_implicit = surfname && strncmp(surfname, IMPLICIT_PREFIX, strlen(IMPLICIT_PREFIX) ) == 0 ;
0376                 int Type = -2 ;
0377                 int Finish = -2 ;
0378                 int ModelValuePercent = -2 ;
0379                 std::string OSN = "-" ;
0380 
0381                 if( is_implicit )
0382                 {
0383                     assert( surf == nullptr ) ;  // not expecting to find surf for implicits
0384                     Type = 1 ;
0385                     Finish = 1 ;
0386                     ModelValuePercent = 100 ;  // placeholders to match old_optical ones
0387                     OSN = "X" ;  // Implicits classified as ordinary Surface as they have bnd/sur entries
0388                 }
0389                 else
0390                 {
0391                     int missing = 0 ;  // -2 better, but use 0 to match old_optical
0392                     Type              = surf ? surf->get_meta<int>("Type",-1) : missing ;
0393                     Finish            = surf ? surf->get_meta<int>("Finish", -1 ) : missing ;
0394                     ModelValuePercent = surf ? int(100.*surf->get_meta<double>("ModelValue", 0.)) : missing ;
0395                     OSN = surf ? surf->get_meta<std::string>("OpticalSurfaceName", "-") : "-" ;
0396                 }
0397 
0398 
0399                 char OSN0 = *OSN.c_str() ;
0400                 int ems = smatsur::TypeFromChar(OSN0) ;
0401 
0402                 /**
0403                 HERE CAN DETECT FINISH AND ModelValuePercent THAT
0404                 REQUIRES SIGMA_ALPHA OR POLISH GROUND SURFACE HANDLING
0405                 FOR WHICH WILL NEED NEW smatsur.h enum value
0406                 **/
0407 
0408                 int Payload_Y = ems ;
0409 
0410                 if(VERBOSE) std::cout
0411                     << " bnd:i "   << std::setw(3) << i
0412                     << " sur:idx " << std::setw(3) << idx
0413                     << " Type " << std::setw(2) << Type
0414                     << " Finish " << std::setw(2) << Finish
0415                     << " MVP " << std::setw(3) << ModelValuePercent
0416                     << " surf " << ( surf ? "YES" : "NO " )
0417                     << " impl " << ( is_implicit ? "YES" : "NO " )
0418                     << " osn0 " << ( OSN0 == '\0' ? '0' : OSN0 )
0419                     << " OSN " << OSN
0420                     << " ems " << ems
0421                     << " emsn " << smatsur::Name(ems)
0422                     << " surfname " << ( surfname ? surfname : "-" )
0423                     << std::endl
0424                     ;
0425 
0426                 op_v[op_index+0] = idx1 ;
0427                 op_v[op_index+1] = Payload_Y ;
0428                 op_v[op_index+2] = Finish ;
0429                 op_v[op_index+3] = ModelValuePercent ;
0430             }
0431         }
0432     }
0433     return op ;
0434 }
0435 
0436 
0437 
0438 
0439 /**
0440 sstandard::make_bnd
0441 ---------------------
0442 
0443 Form bnd array by interleaving mat and sur array entries as directed by vbd int pointers.
0444 
0445 **/
0446 
0447 inline NP* sstandard::make_bnd(
0448     const std::vector<int4>& vbd,
0449     const std::vector<std::string>& bdname,
0450     const NP* mat,
0451     const NP* sur )
0452 {
0453     assert( mat->shape.size() == 4 );
0454     assert( sur->shape.size() == 4 );
0455 
0456     int num_mat = mat->shape[0] ;
0457     int num_sur = sur->shape[0] ;
0458 
0459     for(int d=1 ; d < 4 ; d++) assert( mat->shape[d] == sur->shape[d] ) ;
0460 
0461     assert( mat->shape[1] == sprop::NUM_PAYLOAD_GRP );
0462     int num_domain = mat->shape[2] ;
0463     assert( mat->shape[3] == sprop::NUM_PAYLOAD_VAL );
0464 
0465     const double* mat_v = mat->cvalues<double>();
0466     const double* sur_v = sur->cvalues<double>();
0467 
0468     int num_bnd = vbd.size() ;
0469     int num_bdname = bdname.size() ;
0470 
0471     bool num_bnd_expect = num_bnd == num_bdname ;
0472     if(!num_bnd_expect) std::raise(SIGINT) ;
0473     assert( num_bnd_expect);
0474 
0475     int4 mn ;
0476     int4 mx ;
0477     column_range(mn, mx, vbd );
0478     if(VERBOSE) std::cout << " sstandard::bnd mn " << mn << " mx " << mx << std::endl ;
0479 
0480     bool mat_expect = mx.x < num_mat && mx.w < num_mat ;
0481     bool sur_expect = mx.y < num_sur && mx.z < num_sur ;
0482 
0483     if(!mat_expect) std::raise(SIGINT);
0484     if(!sur_expect) std::raise(SIGINT);
0485 
0486     assert( mat_expect );
0487     assert( sur_expect );
0488 
0489     int ni = num_bnd ;                // ~53
0490     int nj = sprop::NUM_MATSUR ;      //   4  (omat,osur,isur,imat)
0491     int nk = sprop::NUM_PAYLOAD_GRP ; //   2
0492     int nl = num_domain ;             // 761  fine domain
0493     int nn = sprop::NUM_PAYLOAD_VAL ; //   4
0494 
0495     int np = nk*nl*nn ;               // 2*761*4  number of payload values for one mat/sur
0496 
0497 
0498     NP* bnd_ = NP::Make<double>(ni, nj, nk, nl, nn );
0499     bnd_->fill<double>(-1.) ; // trying to match X4/GGeo unfilled
0500     bnd_->set_names( bdname );
0501 
0502     // metadata needed by QBnd::MakeBoundaryTex
0503     bnd_->set_meta<float>("domain_low",  sdomain::DomainLow() );
0504     bnd_->set_meta<float>("domain_high", sdomain::DomainHigh() );
0505     bnd_->set_meta<float>("domain_step", sdomain::DomainStep() );
0506     bnd_->set_meta<float>("domain_range", sdomain::DomainRange() );
0507 
0508     double* bnd_v = bnd_->values<double>() ;
0509 
0510     for(int i=0 ; i < ni ; i++)
0511     {
0512         std::array<int, 4> _bd = {{ vbd[i].x, vbd[i].y, vbd[i].z, vbd[i].w }} ;
0513         for(int j=0 ; j < nj ; j++)
0514         {
0515             int ptr     = _bd[j] ;  // omat,osur,isur,imat index "pointer" into mat or sur arrays
0516             if( ptr < 0 ) continue ;
0517             bool is_mat =  j == 0 || j == 3 ;
0518             bool is_sur =  j == 1 || j == 2 ;
0519             if(is_mat) assert( ptr < num_mat );
0520             if(is_sur) assert( ptr < num_sur );
0521 
0522             int src_index = ptr*np ;
0523             int dst_index = (i*nj + j)*np ;
0524             const double* src_v = is_mat ? mat_v : sur_v ;
0525 
0526             for(int p=0 ; p < np ; p++) bnd_v[dst_index + p] = src_v[src_index + p] ;
0527         }
0528     }
0529     return bnd_  ;
0530 }
0531 
0532 inline void sstandard::column_range(int4& mn, int4& mx,  const std::vector<int4>& vbd)
0533 {
0534     mn.x = std::numeric_limits<int>::max() ;
0535     mn.y = std::numeric_limits<int>::max() ;
0536     mn.z = std::numeric_limits<int>::max() ;
0537     mn.w = std::numeric_limits<int>::max() ;
0538 
0539     mx.x = std::numeric_limits<int>::min() ;
0540     mx.y = std::numeric_limits<int>::min() ;
0541     mx.z = std::numeric_limits<int>::min() ;
0542     mx.w = std::numeric_limits<int>::min() ;
0543 
0544     int num = vbd.size();
0545     for(int i=0 ; i < num ; i++)
0546     {
0547         const int4& b = vbd[i] ;
0548         if(b.x > mx.x) mx.x = b.x ;
0549         if(b.y > mx.y) mx.y = b.y ;
0550         if(b.z > mx.z) mx.z = b.z ;
0551         if(b.w > mx.w) mx.w = b.w ;
0552 
0553         if(b.x < mn.x) mn.x = b.x ;
0554         if(b.y < mn.y) mn.y = b.y ;
0555         if(b.z < mn.z) mn.z = b.z ;
0556         if(b.w < mn.w) mn.w = b.w ;
0557     }
0558 }
0559 
0560 /**
0561 sstandard::unused_mat
0562 -------------------------
0563 
0564 This now done at U4Tree.h level with U4Material::MakeStandardArray
0565 to allow use of Geant4 interpolation.
0566 
0567 This operates from the NPFold props using NP interpolation.
0568 In principal it should give equivalent results to Geant4 interpolation.
0569 However its simpler to just use Geant4 interpolation from U4Tree level.
0570 
0571 **/
0572 inline NP* sstandard::unused_mat( const std::vector<std::string>& names, const NPFold* fold )
0573 {
0574     assert(0);
0575     const sproplist* pl = sproplist::Material() ;
0576     return unused_create(pl, names, fold );
0577 }
0578 
0579 /**
0580 sstandard::unused_sur
0581 -----------------------
0582 
0583 This is now done with U4Tree::initSurfaces_Serialize using U4SurfaceArray
0584 
0585 Note that because the sur array is not a one-to-one from properties
0586 like the mat array this approach is anyhow unworkable as it stands.
0587 
0588 **/
0589 
0590 inline NP* sstandard::unused_sur( const std::vector<std::string>& names, const NPFold* fold )
0591 {
0592     assert(0);
0593     const sproplist* pl = sproplist::Surface() ;
0594     return unused_create(pl, names, fold );
0595 }
0596 
0597 /**
0598 sstandard::unused_create
0599 --------------------------
0600 
0601 This assumes simple one-to-one relationship between the props
0602 and the array content. That is true for "mat" but not for "sur"
0603 
0604 **/
0605 
0606 inline NP* sstandard::unused_create(const sproplist* pl, const std::vector<std::string>& names, const NPFold* fold )
0607 {
0608     assert(0);
0609     sdomain dom ;
0610 
0611     int ni = names.size() ;
0612     int nj = sprop::NUM_PAYLOAD_GRP ;
0613     int nk = dom.length ;
0614     int nl = sprop::NUM_PAYLOAD_VAL ;
0615 
0616     NP* sta = NP::Make<double>(ni, nj, nk, nl) ;
0617     sta->set_names(names);
0618     double* sta_v = sta->values<double>();
0619 
0620     std::cout << "sstandard::create sta.sstr " << sta->sstr() << std::endl ;
0621 
0622     for(int i=0 ; i < ni ; i++ )               // names
0623     {
0624         const char* name = names[i].c_str() ;
0625         NPFold* sub = fold->get_subfold(name) ;
0626 
0627         std::cout
0628             << std::setw(4) << i
0629             << " : "
0630             << std::setw(60) << name
0631             << " : "
0632             << sub->stats()
0633             << std::endl
0634             ;
0635 
0636         for(int j=0 ; j < nj ; j++)           // payload groups
0637         {
0638             for(int k=0 ; k < nk ; k++)       // wavelength
0639             {
0640                 //double wavelength_nm = dom.wavelength_nm[k] ;
0641                 double energy_eV = dom.energy_eV[k] ;
0642                 double energy = energy_eV * 1.e-6 ;  // Geant4 actual energy unit is MeV
0643 
0644                 for(int l=0 ; l < nl ; l++)   // payload values
0645                 {
0646                     const sprop* prop = pl->get(j,l) ;
0647                     assert( prop );
0648 
0649                     const char* pn = prop->name ;
0650                     const NP* a = sub->get(pn) ;
0651                     double value = a ? a->interp( energy ) : prop->def ;
0652 
0653                     int index = i*nj*nk*nl + j*nk*nl + k*nl + l ;
0654                     sta_v[index] = value ;
0655                 }
0656             }
0657         }
0658     }
0659     return sta ;
0660 }
0661 
0662