#ifndef NEURONET_RAG_MEMORY_MQH
#define NEURONET_RAG_MEMORY_MQH

CRAGInferenceSet::CRAGInferenceSet(void) : opencl(NULL),centroids(NULL),used(NULL),
   scores(NULL),partial(NULL),merge(NULL),top(NULL),actions(NULL),action_used(NULL),
   action_mean_reward(NULL),action_hit_count(NULL),absolute_relevance(NULL),tokens(NULL),
   fingerprint(0)
  {
  }

CRAGInferenceSet::~CRAGInferenceSet(void)
  {
   if(centroids!=NULL) delete centroids;
   if(used!=NULL) delete used;
   if(scores!=NULL) delete scores;
   if(partial!=NULL) delete partial;
   if(merge!=NULL) delete merge;
   if(top!=NULL) delete top;
   if(actions!=NULL) delete actions;
   if(action_used!=NULL) delete action_used;
   if(action_mean_reward!=NULL) delete action_mean_reward;
   if(action_hit_count!=NULL) delete action_hit_count;
   if(absolute_relevance!=NULL) delete absolute_relevance;
   if(tokens!=NULL) delete tokens;
   opencl=NULL;
  }

// CLayerDescription mapping for defNeuronRAGMemory:
// window=scenario embedding, window_out=action dimension, count=token width,
// layers=TopK, units[0]=scenario capacity, heads[0]=action capacity.
// This is structural model metadata. OnlineMemorySize is runtime-only.
CNeuronRAGMemory::CNeuronRAGMemory(void) : iScenarioEmbedding(0),
   iActionDimension(0),iTokenWidth(0),iScenarioCapacity(0),
   iActionCapacity(0),iTopK(0),iOnlineMemorySize(0),iScenarioCount(0),
   iFormatVersion(2),m_inference(NULL),iPublicationGeneration(0),iNextPendingId(1)
  {
  }

CNeuronRAGMemory::CNeuronRAGMemory(CLayerDescription &description) : iScenarioEmbedding(0),
   iActionDimension(0),iTokenWidth(0),iScenarioCapacity(0),
   iActionCapacity(0),iTopK(0),iOnlineMemorySize(0),iScenarioCount(0),
   iFormatVersion(2),m_inference(NULL),iPublicationGeneration(0),iNextPendingId(1)
  {
   Configure(description);
  }

CNeuronRAGMemory::~CNeuronRAGMemory(void)
  {
   if(m_inference!=NULL)
      delete m_inference;
   m_inference=NULL;
   ArrayResize(pending_records,0);
   ArrayResize(pending_facts,0);
  }

bool CNeuronRAGMemory::Configure(CLayerDescription &description)
  {
   if(description.type!=defNeuronRAGMemory || description.window==0 ||
      description.window_out==0 || description.count==0 || description.layers==0 ||
      ArraySize(description.units)<1 || ArraySize(description.heads)<1 ||
      description.units[0]==0 || description.heads[0]==0 ||
      description.count!=description.window_out+2 ||
      description.layers>RAG_TOPK_MAX || description.units[0]>RAG_FLOAT_INDEX_LIMIT ||
      description.units[0]>=(uint)INT_MAX || description.heads[0]>=(uint)INT_MAX)
      ReturnFalse;
   iScenarioEmbedding=description.window;
   iActionDimension=description.window_out;
   iTokenWidth=description.count;
   iTopK=description.layers;
   iScenarioCapacity=description.units[0];
   iActionCapacity=description.heads[0];
   return true;
  }

void CNeuronRAGMemory::ClearTerminal(SRAGTerminalOutcome &terminal) const
  {
   terminal.relative_return=0.0f;
   terminal.max_drawdown=0.0f;
   terminal.costs=0.0f;
   terminal.risk_usage=0.0f;
   terminal.duration=0.0f;
   terminal.reward=0.0f;
  }

bool CNeuronRAGMemory::NormalizeActionDescriptor(const float &source[],const double equity,
                                                  const double risk_budget,const double point_cost,
                                                  const double point,const double volatility,
                                                  const double volatility_range,
                                                  const double maximum_take_profit,
                                                  const double maximum_stop_loss,
                                                  float &destination[]) const
  {
   ArrayInitialize(destination,0.0f);
   // The generic RAG layer owns the numerical canonical descriptor, but its
   // caller owns the interpretation and supplies every scale explicitly.
   if(iActionDimension!=6 || ArraySize(source)!=(int)iActionDimension ||
      ArraySize(destination)!=(int)iActionDimension || equity<=0 || risk_budget<=0 ||
      point_cost<=0 || point<=0 || volatility<=0 || volatility_range<=0 ||
      maximum_take_profit<0 || maximum_stop_loss<0 ||
      !MathIsValidNumber(equity) || !MathIsValidNumber(risk_budget) ||
      !MathIsValidNumber(point_cost) || !MathIsValidNumber(point) ||
      !MathIsValidNumber(volatility) || !MathIsValidNumber(volatility_range) ||
      !MathIsValidNumber(maximum_take_profit) || !MathIsValidNumber(maximum_stop_loss))
      return false;
   for(uint element=0;element<iActionDimension;element++)
      if(!MathIsValidNumber(source[element]))
         return false;
   const double scale=MathMax(volatility*volatility_range,point);
   const double buy_sl=MathMax(double(source[2]),0.0)*maximum_stop_loss*point;
   const double sell_sl=MathMax(double(source[5]),0.0)*maximum_stop_loss*point;
   const double buy_tp=MathMax(double(source[1]),0.0)*maximum_take_profit*point;
   const double sell_tp=MathMax(double(source[4]),0.0)*maximum_take_profit*point;
   destination[0]=float(MathMax(MathMin(double(source[0])*buy_sl*point_cost/risk_budget,1.0),0.0));
   destination[1]=float(MathMax(MathMin(buy_tp/scale,1.0),0.0));
   destination[2]=float(MathMax(MathMin(buy_sl/scale,1.0),0.0));
   destination[3]=float(MathMax(MathMin(double(source[3])*sell_sl*point_cost/risk_budget,1.0),0.0));
   destination[4]=float(MathMax(MathMin(sell_tp/scale,1.0),0.0));
   destination[5]=float(MathMax(MathMin(sell_sl/scale,1.0),0.0));
   return true;
  }

void CNeuronRAGMemory::NormalizeTerminalOutcome(const double reward,const double max_drawdown,
                                                 const double costs,const double risk_usage,
                                                 const double duration,const double starting_equity,
                                                 const double risk_budget,const double duration_scale,
                                                 SRAGTerminalOutcome &result) const
  {
   ClearTerminal(result);
   const bool equity_valid=(MathIsValidNumber(starting_equity) && starting_equity>0);
   const bool risk_budget_valid=(MathIsValidNumber(risk_budget) && risk_budget>0);
   const bool duration_scale_valid=(MathIsValidNumber(duration_scale) && duration_scale>0);
   if(equity_valid)
     {
      if(MathIsValidNumber(reward))
        {
         result.relative_return=float(reward/starting_equity);
         result.reward=result.relative_return;
        }
      if(MathIsValidNumber(max_drawdown))
         result.max_drawdown=float(MathMax(max_drawdown,0.0)/starting_equity);
      if(MathIsValidNumber(costs))
         result.costs=float(MathMax(costs,0.0)/starting_equity);
     }
   if(risk_budget_valid && MathIsValidNumber(risk_usage))
      result.risk_usage=float(MathMax(MathMin(MathMax(risk_usage,0.0)/risk_budget,1.0),0.0));
   if(duration_scale_valid && MathIsValidNumber(duration))
      result.duration=float(MathMax(MathMin(MathMax(duration,0.0)/duration_scale,1.0),0.0));
  }

bool CNeuronRAGMemory::InitializeAction(SRAGActionCentroid &action,const uint scenario_id) const
  {
   action.used=false;
   action.scenario_id=scenario_id;
   action.hit_count=0;
   action.mean_reward=0.0f;
   ClearTerminal(action.mean_terminal);
   if(ArrayResize(action.centroid,(int)iActionDimension)!=(int)iActionDimension)
      return false;
   ArrayInitialize(action.centroid,0.0f);
   return true;
  }

bool CNeuronRAGMemory::InitializeScenario(SRAGScenarioCentroid &scenario,const uint scenario_id) const
  {
   scenario.used=false;
   scenario.scenario_id=scenario_id;
   scenario.hit_count=0;
   if(ArrayResize(scenario.centroid,(int)iScenarioEmbedding)!=(int)iScenarioEmbedding)
      return false;
   ArrayInitialize(scenario.centroid,0.0f);
   if(ArrayResize(scenario.actions,(int)iActionCapacity)!=(int)iActionCapacity)
      return false;
   for(uint action=0;action<iActionCapacity;action++)
      if(!InitializeAction(scenario.actions[action],scenario_id))
         return false;
   return true;
  }

bool CNeuronRAGMemory::CopyAction(const SRAGActionCentroid &source,
                                  SRAGActionCentroid &destination) const
  {
   if(!ValidAction(source.centroid) ||
      ArrayResize(destination.centroid,(int)iActionDimension)!=(int)iActionDimension)
      return false;
   destination.used=source.used;
   destination.scenario_id=source.scenario_id;
   destination.hit_count=source.hit_count;
   destination.mean_reward=source.mean_reward;
   destination.mean_terminal=source.mean_terminal;
   ArrayCopy(destination.centroid,source.centroid);
   return true;
  }

bool CNeuronRAGMemory::CopyScenario(const SRAGScenarioCentroid &source,
                                     SRAGScenarioCentroid &destination) const
  {
   if(ArraySize(source.centroid)!=(int)iScenarioEmbedding ||
      ArraySize(source.actions)!=(int)iActionCapacity ||
      ArrayResize(destination.centroid,(int)iScenarioEmbedding)!=(int)iScenarioEmbedding ||
      ArrayResize(destination.actions,(int)iActionCapacity)!=(int)iActionCapacity)
      return false;
   destination.used=source.used;
   destination.scenario_id=source.scenario_id;
   destination.hit_count=source.hit_count;
   ArrayCopy(destination.centroid,source.centroid);
   for(uint action=0;action<iActionCapacity;action++)
      if(!CopyAction(source.actions[action],destination.actions[action]))
         return false;
   return true;
  }

bool CNeuronRAGMemory::CopyScenarioFlexible(const SRAGScenarioCentroid &source,
                                             SRAGScenarioCentroid &destination) const
  {
   const int action_total=ArraySize(source.actions);
   if(ArraySize(source.centroid)!=(int)iScenarioEmbedding ||
      action_total<(int)iActionCapacity ||
      ArrayResize(destination.centroid,(int)iScenarioEmbedding)!=(int)iScenarioEmbedding ||
      ArrayResize(destination.actions,action_total)!=action_total)
      return false;
   destination.used=source.used;
   destination.scenario_id=source.scenario_id;
   destination.hit_count=source.hit_count;
   ArrayCopy(destination.centroid,source.centroid);
   for(int action=0;action<action_total;action++)
      if(!CopyAction(source.actions[action],destination.actions[action]))
         return false;
   return true;
  }

bool CNeuronRAGMemory::AppendActionCandidate(const uint scenario_id,
                                              SRAGScenarioCentroid &scenario,
                                              const SRAGActionCentroid &incoming) const
  {
   if(scenario_id>=iScenarioCapacity || !scenario.used || !incoming.used ||
      ArraySize(incoming.centroid)!=(int)iActionDimension)
      return false;
   const int current=ArraySize(scenario.actions);
   for(int action=0;action<current;action++)
      if(!scenario.actions[action].used)
        {
         if(!CopyAction(incoming,scenario.actions[action]))
            return false;
         scenario.actions[action].scenario_id=scenario_id;
         return true;
        }
   if(current<0 || current>=65536 || ArrayResize(scenario.actions,current+1)!=current+1 ||
      !InitializeAction(scenario.actions[current],scenario_id) ||
      !CopyAction(incoming,scenario.actions[current]))
      return false;
   scenario.actions[current].scenario_id=scenario_id;
   return true;
  }

bool CNeuronRAGMemory::CompactActionCandidates(SRAGScenarioCentroid &scenario) const
  {
   SRAGActionCentroid compacted[];
   if(ArrayResize(compacted,(int)iActionCapacity)!=(int)iActionCapacity)
      return false;
   for(uint action=0;action<iActionCapacity;action++)
      if(!InitializeAction(compacted[action],scenario.scenario_id))
         return false;
   uint destination=0;
   for(int action=0;action<ArraySize(scenario.actions);action++)
      if(scenario.actions[action].used)
        {
         if(destination>=iActionCapacity ||
            !CopyAction(scenario.actions[action],compacted[destination]))
            return false;
         compacted[destination].scenario_id=scenario.scenario_id;
         destination++;
        }
   ArraySwap(scenario.actions,compacted);
   return true;
  }

bool CNeuronRAGMemory::NormalizeL2(const float &source[],float &destination[]) const
  {
   const int total=ArraySize(source);
   if(total<=0 || ArrayResize(destination,total)!=total)
      return false;
   double square_sum=0.0;
   for(int i=0;i<total;i++)
     {
      if(!MathIsValidNumber(source[i]))
         return false;
      square_sum+=(double)source[i]*source[i];
     }
   if(!MathIsValidNumber(square_sum) || square_sum<=DBL_EPSILON)
      return false;
   const double length=MathSqrt(square_sum);
   for(int i=0;i<total;i++)
      destination[i]=(float)(source[i]/length);
   return true;
  }

float CNeuronRAGMemory::SquaredDistance(const float &left[],const float &right[]) const
  {
   const float max_float=3.402823466e+38f;
   if(ArraySize(left)!=ArraySize(right) || ArraySize(left)<=0)
      return max_float;
   float distance=0.0f;
   for(int i=0;i<ArraySize(left);i++)
     {
      if(!MathIsValidNumber(left[i]) || !MathIsValidNumber(right[i]))
         return max_float;
      const float delta=(float)(left[i]-right[i]);
      const float squared=(float)(delta*delta);
      distance=(float)(distance+squared);
     }
   return (MathIsValidNumber(distance) ? distance : max_float);
  }

bool CNeuronRAGMemory::ValidAction(const float &action[]) const
  {
   if(ArraySize(action)!=(int)iActionDimension)
      return false;
   for(uint element=0;element<iActionDimension;element++)
      if(!MathIsValidNumber(action[element]) || action[element]<0.0f || action[element]>1.0f)
         return false;
   return true;
  }

bool CNeuronRAGMemory::ValidTerminal(const SRAGTerminalOutcome &terminal) const
  {
   return (MathIsValidNumber(terminal.relative_return) &&
           MathIsValidNumber(terminal.max_drawdown) &&
           MathIsValidNumber(terminal.costs) &&
           MathIsValidNumber(terminal.risk_usage) &&
           MathIsValidNumber(terminal.duration) &&
           MathIsValidNumber(terminal.reward) &&
           terminal.max_drawdown>=0.0f && terminal.costs>=0.0f &&
           terminal.risk_usage>=0.0f && terminal.risk_usage<=1.0f &&
           terminal.duration>=0.0f && terminal.duration<=1.0f &&
           MathAbs(terminal.reward-terminal.relative_return)<=1.0e-6f);
  }

void CNeuronRAGMemory::MergeTerminal(SRAGTerminalOutcome &destination,
                                     const ulong destination_hits,
                                     const SRAGTerminalOutcome &source,
                                     const ulong source_hits) const
  {
   const double total=(double)destination_hits+(double)source_hits;
   destination.relative_return=(float)((destination.relative_return*destination_hits+
                                        source.relative_return*source_hits)/total);
   destination.max_drawdown=(float)((destination.max_drawdown*destination_hits+
                                     source.max_drawdown*source_hits)/total);
   destination.costs=(float)((destination.costs*destination_hits+
                              source.costs*source_hits)/total);
   destination.risk_usage=(float)((destination.risk_usage*destination_hits+
                                   source.risk_usage*source_hits)/total);
   destination.duration=(float)((destination.duration*destination_hits+
                                 source.duration*source_hits)/total);
   destination.reward=(float)((destination.reward*destination_hits+
                               source.reward*source_hits)/total);
  }

int CNeuronRAGMemory::FirstFreeScenario() const
  {
   for(uint scenario=0;scenario<iScenarioCapacity;scenario++)
      if(!scenarios[scenario].used)
         return (int)scenario;
   return -1;
  }

int CNeuronRAGMemory::FirstFreeAction(const uint scenario_id) const
  {
   if(scenario_id>=iScenarioCapacity || !scenarios[scenario_id].used)
      return -1;
   for(uint action=0;action<iActionCapacity;action++)
      if(!scenarios[scenario_id].actions[action].used)
         return (int)action;
   return -1;
  }

bool CNeuronRAGMemory::RebindScenarioActions(SRAGScenarioCentroid &scenario,
                                              const uint destination_scenario_id) const
  {
   scenario.scenario_id=destination_scenario_id;
   for(int action=0;action<ArraySize(scenario.actions);action++)
      scenario.actions[action].scenario_id=destination_scenario_id;
   return true;
  }

bool CNeuronRAGMemory::MergeActionCandidates(const uint scenario_id,
                                              SRAGActionCentroid &destination,
                                              const SRAGActionCentroid &source) const
  {
   if(!destination.used || !source.used || destination.hit_count==0 || source.hit_count==0 ||
      !ValidAction(destination.centroid) || !ValidAction(source.centroid) ||
      !ValidTerminal(destination.mean_terminal) || !ValidTerminal(source.mean_terminal) ||
      !MathIsValidNumber(destination.mean_reward) || !MathIsValidNumber(source.mean_reward) ||
      destination.hit_count>ulong(-1)-source.hit_count)
      return false;
   const ulong destination_hits=destination.hit_count;
   const ulong source_hits=source.hit_count;
   const double total=(double)destination_hits+(double)source_hits;
   for(uint element=0;element<iActionDimension;element++)
      destination.centroid[element]=(float)((destination.centroid[element]*destination_hits+
                                             source.centroid[element]*source_hits)/total);
   destination.mean_reward=(float)((destination.mean_reward*destination_hits+
                                    source.mean_reward*source_hits)/total);
   MergeTerminal(destination.mean_terminal,destination_hits,source.mean_terminal,source_hits);
   destination.hit_count=destination_hits+source_hits;
   destination.used=true;
   destination.scenario_id=scenario_id;
   return true;
  }

bool CNeuronRAGMemory::PairCountSafe(const uint candidate_count,int &pair_count) const
  {
   pair_count=0;
   if(candidate_count<2 || candidate_count>65536)
      return false;
   const ulong pairs=((candidate_count&1)==0 ?
                      (ulong)(candidate_count/2)*(ulong)(candidate_count-1) :
                      (ulong)candidate_count*(ulong)((candidate_count-1)/2));
   if(pairs>(ulong)INT_MAX)
      return false;
   pair_count=(int)pairs;
   return true;
  }

// A launch reduces every independent candidate row in parallel.  The device
// returns only one float4 per row; the immutable snapshot itself stays on the
// host until the count-weighted COW transaction has succeeded.
bool CNeuronRAGMemory::FindNearestPairsGPU(const float &candidate_centroids[],
                                           const float &candidate_used[],
                                           const uint candidate_count,const uint dimension,
                                           const uint batch_count,int &destinations[],int &sources[])
  {
   ArrayResize(destinations,0);
   ArrayResize(sources,0);
   int pair_count=0;
   if(OpenCL==NULL || candidate_count<2 || dimension==0 || batch_count==0 ||
      candidate_count>65536 || dimension>(uint)INT_MAX || batch_count>(uint)INT_MAX ||
      batch_count>UINT_MAX/64 || batch_count>(uint)INT_MAX/4 ||
      !PairCountSafe(candidate_count,pair_count) || pair_count<=0 ||
      (ulong)candidate_count*(ulong)batch_count>ULONG_MAX/(ulong)dimension)
      return false;
   const ulong rows=(ulong)candidate_count*(ulong)batch_count;
   const ulong values=rows*(ulong)dimension;
   if(values>(ulong)INT_MAX || rows>(ulong)INT_MAX)
      return false;
   if(ArraySize(candidate_centroids)!=(int)values || ArraySize(candidate_used)!=(int)rows)
      return false;
   if(ArrayResize(destinations,(int)batch_count)!=(int)batch_count ||
      ArrayResize(sources,(int)batch_count)!=(int)batch_count)
      return false;
   ArrayInitialize(destinations,-1);
   ArrayInitialize(sources,-1);

   // BufferCreate reuses the allocation and performs BufferWrite when OpenCL
   // and the shape are unchanged.  BufferInit frees only on a shape change.
   if(!cNearestCentroids.BufferInit((uint)values,0.0f) ||
      !cNearestCentroids.AssignArray(candidate_centroids) ||
      !cNearestCentroids.BufferCreate(OpenCL) ||
      !cNearestUsed.BufferInit((uint)rows,0.0f) ||
      !cNearestUsed.AssignArray(candidate_used) || !cNearestUsed.BufferCreate(OpenCL) ||
      !cNearestResult.BufferInit(batch_count*4,0.0f) || !cNearestResult.BufferCreate(OpenCL))
      return false;
   uint offset[]={0};
   uint global[]={batch_count*64};
   uint local[]={64};
   if(!OpenCL.SetArgumentBuffer(def_k_RAGNearestPair,def_k_ragn_centroids,cNearestCentroids.GetIndex()) ||
      !OpenCL.SetArgumentBuffer(def_k_RAGNearestPair,def_k_ragn_used,cNearestUsed.GetIndex()) ||
      !OpenCL.SetArgumentBuffer(def_k_RAGNearestPair,def_k_ragn_result,cNearestResult.GetIndex()) ||
      !OpenCL.SetArgument(def_k_RAGNearestPair,def_k_ragn_candidate_count,candidate_count) ||
      !OpenCL.SetArgument(def_k_RAGNearestPair,def_k_ragn_dimension,dimension) ||
      !OpenCL.SetArgument(def_k_RAGNearestPair,def_k_ragn_batch_count,batch_count) ||
      !OpenCL.Execute(def_k_RAGNearestPair,1,offset,global,local))
      return false;
   float values_out[];
   if(ArrayResize(values_out,(int)(batch_count*4))!=(int)(batch_count*4) ||
      cNearestResult.GetData(values_out)!=(int)(batch_count*4))
      return false;
   for(uint batch=0;batch<batch_count;batch++)
     {
      const uint row_offset=batch*4;
      if(values_out[row_offset+3]<0.5f || !MathIsValidNumber(values_out[row_offset]))
         return false;
      const int left=(int)(values_out[row_offset+1]+0.5f);
      const int right=(int)(values_out[row_offset+2]+0.5f);
      if(left<0 || right<=left || right>=(int)candidate_count)
         return false;
      destinations[batch]=left;
      sources[batch]=right;
     }
   return true;
  }

bool CNeuronRAGMemory::FindNearestPairGPU(const float &candidate_centroids[],
                                          const float &candidate_used[],
                                          const uint candidate_count,const uint dimension,
                                          const uint batch_count,int &destination,int &source)
  {
   destination=-1;
   source=-1;
   int destinations[],sources[];
   if(!FindNearestPairsGPU(candidate_centroids,candidate_used,candidate_count,dimension,
                           batch_count,destinations,sources) || ArraySize(destinations)<1 ||
      ArraySize(sources)<1)
      return false;
   destination=destinations[0];
   source=sources[0];
   return true;
  }

bool CNeuronRAGMemory::FindNearestActionPair(const uint scenario_id,
                                              const SRAGActionCentroid &action_candidates[],
                                              int &destination,int &source)
  {
   destination=-1;
   source=-1;
   if(OpenCL!=NULL)
     {
      const int candidates=ArraySize(action_candidates);
      float flat[],used[];
      if(candidates<2 || ArrayResize(flat,candidates*(int)iActionDimension)!=candidates*(int)iActionDimension ||
         ArrayResize(used,candidates)!=candidates)
         return false;
      for(int action=0;action<candidates;action++)
        {
         used[action]=(action_candidates[action].used ? 1.0f : 0.0f);
         ArrayCopy(flat,action_candidates[action].centroid,action*(int)iActionDimension,0,(int)iActionDimension);
        }
      return (scenario_id<iScenarioCapacity && FindNearestPairGPU(flat,used,(uint)candidates,
              iActionDimension,1,destination,source));
     }
   const float max_float=3.402823466e+38f;
   float nearest=max_float;
   bool found=false;
   for(int left=0;left<ArraySize(action_candidates);left++)
     {
      if(!action_candidates[left].used)
         continue;
      for(int right=left+1;right<ArraySize(action_candidates);right++)
        {
         if(!action_candidates[right].used)
            continue;
         const float distance=SquaredDistance(action_candidates[left].centroid,
                                               action_candidates[right].centroid);
         if(!found || distance<nearest)
           {
            nearest=distance;
            destination=left;
            source=right;
            found=true;
           }
        }
     }
   return (scenario_id<iScenarioCapacity && destination>=0 && source>=0);
  }

bool CNeuronRAGMemory::ConsolidateActionCandidates(SRAGScenarioCentroid &candidate_snapshot[])
  {
   if(ArraySize(candidate_snapshot)!=(int)iScenarioCapacity)
      return false;
   while(true)
     {
      int active_scenarios[];
      int max_candidates=0;
      for(uint scenario=0;scenario<iScenarioCapacity;scenario++)
        {
         if(!candidate_snapshot[scenario].used)
            continue;
         int used_actions=0;
         const int candidates=ArraySize(candidate_snapshot[scenario].actions);
         if(candidates<0 || candidates>65536)
            return false;
         for(int action=0;action<candidates;action++)
            if(candidate_snapshot[scenario].actions[action].used)
               used_actions++;
         if(used_actions>(int)iActionCapacity)
           {
            const int active=ArraySize(active_scenarios);
            if(ArrayResize(active_scenarios,active+1)!=active+1)
               return false;
            active_scenarios[active]=(int)scenario;
            if(candidates>max_candidates)
               max_candidates=candidates;
           }
        }
      const uint batch_count=(uint)ArraySize(active_scenarios);
      if(batch_count==0)
         break;

      if(OpenCL==NULL)
        {
         for(uint batch=0;batch<batch_count;batch++)
           {
            const int scenario=active_scenarios[batch];
            int destination=-1,source=-1;
            if(!FindNearestActionPair((uint)scenario,candidate_snapshot[scenario].actions,
                                      destination,source) ||
               !MergeActionCandidates((uint)scenario,
                                      candidate_snapshot[scenario].actions[destination],
                                      candidate_snapshot[scenario].actions[source]) ||
               !InitializeAction(candidate_snapshot[scenario].actions[source],(uint)scenario))
               return false;
           }
         continue;
        }

      const ulong rows=(ulong)batch_count*(ulong)max_candidates;
      if(max_candidates<2 || rows>(ulong)INT_MAX ||
         rows>ULONG_MAX/(ulong)iActionDimension ||
         rows*(ulong)iActionDimension>(ulong)INT_MAX)
         return false;
      const int values=(int)(rows*(ulong)iActionDimension);
      float flat[],used[];
      if(ArrayResize(flat,values)!=values || ArrayResize(used,(int)rows)!=(int)rows)
         return false;
      ArrayInitialize(flat,0.0f);
      ArrayInitialize(used,0.0f);
      for(uint batch=0;batch<batch_count;batch++)
        {
         const int scenario=active_scenarios[batch];
         for(int action=0;action<ArraySize(candidate_snapshot[scenario].actions);action++)
           {
            if(!candidate_snapshot[scenario].actions[action].used)
               continue;
            const int slot=(int)batch*max_candidates+action;
            used[slot]=1.0f;
            ArrayCopy(flat,candidate_snapshot[scenario].actions[action].centroid,
                      slot*(int)iActionDimension,0,(int)iActionDimension);
           }
        }
      int destinations[],sources[];
      if(!FindNearestPairsGPU(flat,used,(uint)max_candidates,iActionDimension,batch_count,
                              destinations,sources) ||
         ArraySize(destinations)!=(int)batch_count || ArraySize(sources)!=(int)batch_count)
         return false;
      for(uint batch=0;batch<batch_count;batch++)
        {
         const int scenario=active_scenarios[batch];
         const int destination=destinations[batch];
         const int source=sources[batch];
         if(destination<0 || source<=destination ||
            source>=ArraySize(candidate_snapshot[scenario].actions) ||
            !candidate_snapshot[scenario].actions[destination].used ||
            !candidate_snapshot[scenario].actions[source].used ||
            !MergeActionCandidates((uint)scenario,
                                   candidate_snapshot[scenario].actions[destination],
                                   candidate_snapshot[scenario].actions[source]) ||
            !InitializeAction(candidate_snapshot[scenario].actions[source],(uint)scenario))
            return false;
        }
     }

   for(uint scenario=0;scenario<iScenarioCapacity;scenario++)
      if(!CompactActionCandidates(candidate_snapshot[scenario]) ||
         !RebindScenarioActions(candidate_snapshot[scenario],scenario))
         return false;
   return true;
  }

bool CNeuronRAGMemory::MergeScenarioCandidates(SRAGScenarioCentroid &destination,
                                                const SRAGScenarioCentroid &source)
  {
   if(!destination.used || !source.used || destination.hit_count==0 || source.hit_count==0 ||
      ArraySize(destination.centroid)!=(int)iScenarioEmbedding ||
      ArraySize(source.centroid)!=(int)iScenarioEmbedding ||
      destination.hit_count>ulong(-1)-source.hit_count)
      return false;
   const ulong destination_hits=destination.hit_count;
   const ulong source_hits=source.hit_count;
   float previous_centroid[];
   if(ArrayResize(previous_centroid,(int)iScenarioEmbedding)!=(int)iScenarioEmbedding)
      return false;
   ArrayCopy(previous_centroid,destination.centroid);
   float weighted[];
   if(ArrayResize(weighted,(int)iScenarioEmbedding)!=(int)iScenarioEmbedding)
      return false;
   for(uint element=0;element<iScenarioEmbedding;element++)
      weighted[element]=(float)(destination.centroid[element]*destination_hits+
                                source.centroid[element]*source_hits);
   // Equal and opposite normalized observations have no unique normalized
   // mean. Retain the prior valid centroid while still merging their facts.
   if(!NormalizeL2(weighted,destination.centroid))
      ArrayCopy(destination.centroid,previous_centroid);
   destination.hit_count=destination_hits+source_hits;
   for(int action=0;action<ArraySize(source.actions);action++)
      if(source.actions[action].used &&
         !AppendActionCandidate(destination.scenario_id,destination,source.actions[action]))
         return false;
   return RebindScenarioActions(destination,destination.scenario_id);
  }

bool CNeuronRAGMemory::FindNearestScenarioPair(const SRAGScenarioCentroid &scenario_candidates[],
                                                int &destination,int &source)
  {
   destination=-1;
   source=-1;
   if(OpenCL!=NULL)
     {
      const int candidates=ArraySize(scenario_candidates);
      float flat[],used[];
      if(candidates<2 || ArrayResize(flat,candidates*(int)iScenarioEmbedding)!=candidates*(int)iScenarioEmbedding ||
         ArrayResize(used,candidates)!=candidates)
         return false;
      for(int scenario=0;scenario<candidates;scenario++)
        {
         used[scenario]=(scenario_candidates[scenario].used ? 1.0f : 0.0f);
         ArrayCopy(flat,scenario_candidates[scenario].centroid,scenario*(int)iScenarioEmbedding,0,
                   (int)iScenarioEmbedding);
        }
      return FindNearestPairGPU(flat,used,(uint)candidates,iScenarioEmbedding,1,destination,source);
     }
   const float max_float=3.402823466e+38f;
   float nearest=max_float;
   bool found=false;
   for(int left=0;left<ArraySize(scenario_candidates);left++)
     {
      if(!scenario_candidates[left].used)
         continue;
      for(int right=left+1;right<ArraySize(scenario_candidates);right++)
        {
         if(!scenario_candidates[right].used)
            continue;
         const float distance=SquaredDistance(scenario_candidates[left].centroid,
                                               scenario_candidates[right].centroid);
         if(!found || distance<nearest)
           {
            nearest=distance;
            destination=left;
            source=right;
            found=true;
           }
        }
     }
   return (destination>=0 && source>=0);
  }

bool CNeuronRAGMemory::StageScenarioCandidates(const uint source_scenario_id,
                                                const float &scenario_embedding[],
                                                const float &action[],
                                                const SRAGTerminalOutcome &terminal,
                                                SRAGScenarioCentroid &scenario_candidates[])
  {
   if(ArraySize(scenario_embedding)!=(int)iScenarioEmbedding ||
      !ValidAction(action) || !ValidTerminal(terminal) ||
      ArrayResize(scenario_candidates,iScenarioCapacity+1)!=(int)(iScenarioCapacity+1))
      return false;
   for(uint scenario=0;scenario<iScenarioCapacity;scenario++)
      if(!CopyScenario(scenarios[scenario],scenario_candidates[scenario]))
         return false;
   if(!InitializeScenario(scenario_candidates[iScenarioCapacity],source_scenario_id) ||
      !NormalizeL2(scenario_embedding,scenario_candidates[iScenarioCapacity].centroid))
      return false;
   scenario_candidates[iScenarioCapacity].used=true;
   scenario_candidates[iScenarioCapacity].hit_count=1;
   scenario_candidates[iScenarioCapacity].actions[0].used=true;
   scenario_candidates[iScenarioCapacity].actions[0].scenario_id=source_scenario_id;
   scenario_candidates[iScenarioCapacity].actions[0].hit_count=1;
   scenario_candidates[iScenarioCapacity].actions[0].mean_reward=terminal.reward;
   scenario_candidates[iScenarioCapacity].actions[0].mean_terminal=terminal;
   ArrayCopy(scenario_candidates[iScenarioCapacity].actions[0].centroid,action);
   return true;
  }

bool CNeuronRAGMemory::AddCompletedScenario(const uint source_scenario_id,
                                             const float &scenario_embedding[],
                                             const float &action[],
                                             const SRAGTerminalOutcome &terminal)
  {
   SRAGScenarioCentroid candidate_snapshot[];
   uint candidate_count=iScenarioCount;
   if(!CopySnapshot(scenarios,candidate_snapshot) ||
      !ApplyCompletedRecordToSnapshot(candidate_snapshot,candidate_count,source_scenario_id,
                                      scenario_embedding,action,terminal) ||
      !ConsolidateActionCandidates(candidate_snapshot))
      return false;
   ArraySwap(scenarios,candidate_snapshot);
   iScenarioCount=candidate_count;
   return true;
  }

bool CNeuronRAGMemory::ApplyCompletedRecordToSnapshot(SRAGScenarioCentroid &candidate_snapshot[],
                                                       uint &candidate_count,
                                                       const uint source_scenario_id,
                                                       const float &scenario_embedding[],
                                                       const float &action[],
                                                       const SRAGTerminalOutcome &terminal)
  {
   if(ArraySize(candidate_snapshot)!=(int)iScenarioCapacity || candidate_count>iScenarioCapacity ||
      ArraySize(scenario_embedding)!=(int)iScenarioEmbedding ||
      !ValidAction(action) || !ValidTerminal(terminal))
      return false;
   float normalized[];
   if(!NormalizeL2(scenario_embedding,normalized))
      return false;
   int free_scenario=-1;
   for(uint scenario=0;scenario<iScenarioCapacity;scenario++)
      if(!candidate_snapshot[scenario].used)
        {
         free_scenario=(int)scenario;
         break;
        }
   if(free_scenario>=0)
     {
      SRAGScenarioCentroid incoming;
      if(!InitializeScenario(incoming,source_scenario_id))
         return false;
      incoming.used=true;
      incoming.hit_count=1;
      ArrayCopy(incoming.centroid,normalized);
      incoming.actions[0].used=true;
      incoming.actions[0].scenario_id=source_scenario_id;
      incoming.actions[0].hit_count=1;
      incoming.actions[0].mean_reward=terminal.reward;
      incoming.actions[0].mean_terminal=terminal;
      ArrayCopy(incoming.actions[0].centroid,action);
      RebindScenarioActions(incoming,(uint)free_scenario);
      if(!CopyScenario(incoming,candidate_snapshot[free_scenario]))
         return false;
      candidate_count++;
      return true;
     }

   SRAGScenarioCentroid scenario_candidates[];
   if(ArrayResize(scenario_candidates,(int)(iScenarioCapacity+1))!=(int)(iScenarioCapacity+1))
      return false;
   for(uint scenario=0;scenario<iScenarioCapacity;scenario++)
      if(!CopyScenarioFlexible(candidate_snapshot[scenario],scenario_candidates[scenario]))
         return false;
   if(!InitializeScenario(scenario_candidates[iScenarioCapacity],source_scenario_id))
      return false;
   scenario_candidates[iScenarioCapacity].used=true;
   scenario_candidates[iScenarioCapacity].hit_count=1;
   ArrayCopy(scenario_candidates[iScenarioCapacity].centroid,normalized);
   scenario_candidates[iScenarioCapacity].actions[0].used=true;
   scenario_candidates[iScenarioCapacity].actions[0].scenario_id=source_scenario_id;
   scenario_candidates[iScenarioCapacity].actions[0].hit_count=1;
   scenario_candidates[iScenarioCapacity].actions[0].mean_reward=terminal.reward;
   scenario_candidates[iScenarioCapacity].actions[0].mean_terminal=terminal;
   ArrayCopy(scenario_candidates[iScenarioCapacity].actions[0].centroid,action);
   int destination=-1;
   int source=-1;
   if(!FindNearestScenarioPair(scenario_candidates,destination,source) ||
      !MergeScenarioCandidates(scenario_candidates[destination],scenario_candidates[source]))
      return false;
   RebindScenarioActions(scenario_candidates[destination],(uint)destination);
    if(!CopyScenarioFlexible(scenario_candidates[destination],candidate_snapshot[destination]))
      return false;
   if(source==(int)iScenarioCapacity)
      return true;

   RebindScenarioActions(scenario_candidates[iScenarioCapacity],(uint)source);
    if(!CopyScenarioFlexible(scenario_candidates[iScenarioCapacity],candidate_snapshot[source]))
      return false;
   return true;
  }

bool CNeuronRAGMemory::ValidateSnapshot(const SRAGScenarioCentroid &snapshot[],
                                        const uint scenario_count,
                                        const uint scenario_embedding,
                                        const uint action_dimension,
                                        const uint scenario_capacity,
                                        const uint action_capacity) const
  {
   if(scenario_embedding==0 || action_dimension==0 || scenario_capacity==0 || action_capacity==0 ||
      scenario_capacity>(uint)INT_MAX || action_capacity>(uint)INT_MAX ||
      ArraySize(snapshot)!=(int)scenario_capacity || scenario_count>scenario_capacity)
      return false;
   uint used_scenarios=0;
   for(uint scenario=0;scenario<scenario_capacity;scenario++)
     {
      if(snapshot[scenario].scenario_id!=scenario ||
         ArraySize(snapshot[scenario].centroid)!=(int)scenario_embedding ||
         ArraySize(snapshot[scenario].actions)!=(int)action_capacity)
         return false;
      double norm=0.0;
      for(uint element=0;element<scenario_embedding;element++)
        {
         if(!MathIsValidNumber(snapshot[scenario].centroid[element]))
            return false;
         norm+=(double)snapshot[scenario].centroid[element]*snapshot[scenario].centroid[element];
        }
      if(snapshot[scenario].used)
        {
         if(snapshot[scenario].hit_count==0 || norm<0.999 || norm>1.001)
            return false;
         used_scenarios++;
        }
      else if(snapshot[scenario].hit_count!=0 || norm>DBL_EPSILON)
         return false;
      for(uint action=0;action<action_capacity;action++)
        {
         if(snapshot[scenario].actions[action].scenario_id!=scenario ||
            ArraySize(snapshot[scenario].actions[action].centroid)!=(int)action_dimension ||
            !MathIsValidNumber(snapshot[scenario].actions[action].mean_reward) ||
            !ValidTerminal(snapshot[scenario].actions[action].mean_terminal))
            return false;
         for(uint element=0;element<action_dimension;element++)
            if(!MathIsValidNumber(snapshot[scenario].actions[action].centroid[element]) ||
               snapshot[scenario].actions[action].centroid[element]<0.0f ||
               snapshot[scenario].actions[action].centroid[element]>1.0f)
               return false;
         if(snapshot[scenario].actions[action].used &&
            (!snapshot[scenario].used || snapshot[scenario].actions[action].hit_count==0))
            return false;
         if(!snapshot[scenario].actions[action].used &&
            snapshot[scenario].actions[action].hit_count!=0)
            return false;
        }
     }
   return (used_scenarios==scenario_count);
  }

bool CNeuronRAGMemory::CommitSnapshot(const SRAGScenarioCentroid &snapshot[],
                                      const uint scenario_count)
  {
   SRAGScenarioCentroid committed[];
   if(ArrayResize(committed,(int)iScenarioCapacity)!=(int)iScenarioCapacity)
      return false;
   for(uint scenario=0;scenario<iScenarioCapacity;scenario++)
      if(!CopyScenario(snapshot[scenario],committed[scenario]))
         return false;
   ArraySwap(scenarios,committed);
   iScenarioCount=scenario_count;
   return true;
  }

bool CNeuronRAGMemory::CopyCompletedRecord(const SRAGCompletedRecord &source,
                                           SRAGCompletedRecord &destination) const
  {
   if(ArraySize(source.scenario_embedding)!=(int)iScenarioEmbedding ||
      !ValidAction(source.action) || !ValidTerminal(source.terminal) ||
      ArrayResize(destination.scenario_embedding,(int)iScenarioEmbedding)!=(int)iScenarioEmbedding ||
      ArrayResize(destination.action,(int)iActionDimension)!=(int)iActionDimension)
      return false;
   ArrayCopy(destination.scenario_embedding,source.scenario_embedding);
   ArrayCopy(destination.action,source.action);
   destination.terminal=source.terminal;
   return true;
  }

bool CNeuronRAGMemory::CopyPendingEvent(const SRAGPendingEvent &source,
                                        SRAGPendingEvent &destination) const
  {
   if(!ValidAction(source.action) ||
      ArrayResize(destination.action,(int)iActionDimension)!=(int)iActionDimension)
      return false;
   for(uint element=0;element<iActionDimension;element++)
      if(!MathIsValidNumber(source.action[element]))
         return false;
   ArrayCopy(destination.action,source.action);
   destination.confirmed=source.confirmed;
   destination.deal=source.deal;
   destination.position=source.position;
   return (!destination.confirmed || (destination.deal>0 && destination.position>0));
  }

bool CNeuronRAGMemory::CopyPendingFact(const SRAGPendingFact &source,
                                       SRAGPendingFact &destination) const
  {
   const int event_count=ArraySize(source.events);
   if(source.id==0 || event_count<=0 || event_count>=INT_MAX ||
      ArraySize(source.scenario_embedding)!=(int)iScenarioEmbedding ||
      ArrayResize(destination.scenario_embedding,(int)iScenarioEmbedding)!=(int)iScenarioEmbedding ||
      ArrayResize(destination.events,event_count)!=event_count)
      return false;
   for(uint element=0;element<iScenarioEmbedding;element++)
      if(!MathIsValidNumber(source.scenario_embedding[element]))
         return false;
   ArrayCopy(destination.scenario_embedding,source.scenario_embedding);
   destination.id=source.id;
   for(int event=0;event<event_count;event++)
      if(!CopyPendingEvent(source.events[event],destination.events[event]))
         return false;
   return true;
  }

int CNeuronRAGMemory::FindPendingFact(const ulong pending_id) const
  {
   if(pending_id==0)
      return -1;
   for(int fact=0;fact<ArraySize(pending_facts);fact++)
      if(pending_facts[fact].id==pending_id)
         return fact;
   return -1;
  }

bool CNeuronRAGMemory::BuildRemainingPendingFacts(const int excluded,
                                                  SRAGPendingFact &remaining[]) const
  {
   const int total=ArraySize(pending_facts);
   if(excluded<0 || excluded>=total || ArrayResize(remaining,total-1)!=total-1)
      return false;
   int destination=0;
   for(int source=0;source<total;source++)
     {
      if(source==excluded)
         continue;
      if(!CopyPendingFact(pending_facts[source],remaining[destination]))
         return false;
      destination++;
     }
   return true;
  }

bool CNeuronRAGMemory::CopySnapshot(const SRAGScenarioCentroid &source[],
                                    SRAGScenarioCentroid &destination[]) const
  {
   if(ArraySize(source)!=(int)iScenarioCapacity ||
      ArrayResize(destination,(int)iScenarioCapacity)!=(int)iScenarioCapacity)
      return false;
   for(uint scenario=0;scenario<iScenarioCapacity;scenario++)
      if(!CopyScenario(source[scenario],destination[scenario]))
         return false;
   return true;
  }

ulong CNeuronRAGMemory::HashValue(const ulong hash,const ulong value) const
  {
   return (hash^value)*1099511628211;
  }

ulong CNeuronRAGMemory::HashFloatBits(const ulong hash,const float value) const
  {
   URAGFloatBits raw;
   raw.value=value;
   return HashValue(hash,raw.bits);
  }

ulong CNeuronRAGMemory::SnapshotFingerprint(const SRAGScenarioCentroid &snapshot[],
                                            const uint scenario_count) const
  {
   ulong hash=1469598103934665603;
   hash=HashValue(hash,iFormatVersion);
   hash=HashValue(hash,iScenarioEmbedding);
   hash=HashValue(hash,iActionDimension);
   hash=HashValue(hash,iTokenWidth);
   hash=HashValue(hash,iScenarioCapacity);
   hash=HashValue(hash,iActionCapacity);
   hash=HashValue(hash,iTopK);
   hash=HashValue(hash,iOnlineMemorySize);
   hash=HashValue(hash,scenario_count);
   for(uint scenario=0;scenario<iScenarioCapacity;scenario++)
     {
      hash=HashValue(hash,(snapshot[scenario].used ? 1 : 0));
      hash=HashValue(hash,snapshot[scenario].scenario_id);
      hash=HashValue(hash,snapshot[scenario].hit_count);
      for(uint element=0;element<iScenarioEmbedding;element++)
         hash=HashFloatBits(hash,snapshot[scenario].centroid[element]);
      for(uint action=0;action<iActionCapacity;action++)
        {
         hash=HashValue(hash,(snapshot[scenario].actions[action].used ? 1 : 0));
         hash=HashValue(hash,snapshot[scenario].actions[action].scenario_id);
         hash=HashValue(hash,snapshot[scenario].actions[action].hit_count);
         hash=HashFloatBits(hash,snapshot[scenario].actions[action].mean_reward);
         hash=HashFloatBits(hash,snapshot[scenario].actions[action].mean_terminal.relative_return);
         hash=HashFloatBits(hash,snapshot[scenario].actions[action].mean_terminal.max_drawdown);
         hash=HashFloatBits(hash,snapshot[scenario].actions[action].mean_terminal.costs);
         hash=HashFloatBits(hash,snapshot[scenario].actions[action].mean_terminal.risk_usage);
         hash=HashFloatBits(hash,snapshot[scenario].actions[action].mean_terminal.duration);
         hash=HashFloatBits(hash,snapshot[scenario].actions[action].mean_terminal.reward);
         for(uint element=0;element<iActionDimension;element++)
            hash=HashFloatBits(hash,snapshot[scenario].actions[action].centroid[element]);
        }
     }
   return (hash==0 ? 1 : hash);
  }

bool CNeuronRAGMemory::BuildInferenceSet(const SRAGScenarioCentroid &snapshot[],
                                         const ulong fingerprint,
                                         CRAGInferenceSet *&result) const
  {
   result=NULL;
   if(OpenCL==NULL || ArraySize(snapshot)!=(int)iScenarioCapacity)
      return false;
   const ulong centroid_total=(ulong)iScenarioCapacity*iScenarioEmbedding;
   const ulong action_slots=(ulong)iScenarioCapacity*iActionCapacity;
   if(action_slots>ULONG_MAX/iActionDimension)
      return false;
   const ulong action_total=action_slots*iActionDimension;
   const uint partial_count=(iScenarioCapacity+63)/64;
   const ulong partial_total=(ulong)partial_count*iTopK*2;
   const ulong top_total=(ulong)iTopK*2;
   const ulong top_actions=(ulong)iTopK*iActionCapacity;
   if(top_actions>ULONG_MAX/iTokenWidth)
      return false;
   const ulong token_total=top_actions*iTokenWidth;
   if(centroid_total>(ulong)INT_MAX || action_slots>(ulong)INT_MAX ||
      action_total>(ulong)INT_MAX || partial_total>(ulong)INT_MAX ||
      top_total>(ulong)INT_MAX || token_total>(ulong)INT_MAX)
      return false;

   float flat_centroids[],flat_used[],flat_actions[],flat_action_used[];
   float flat_action_mean_reward[],flat_action_hit_count[];
   if(ArrayResize(flat_centroids,(int)centroid_total)!=(int)centroid_total ||
      ArrayResize(flat_used,(int)iScenarioCapacity)!=(int)iScenarioCapacity ||
      ArrayResize(flat_actions,(int)action_total)!=(int)action_total ||
      ArrayResize(flat_action_used,(int)action_slots)!=(int)action_slots ||
      ArrayResize(flat_action_mean_reward,(int)action_slots)!=(int)action_slots ||
      ArrayResize(flat_action_hit_count,(int)action_slots)!=(int)action_slots)
      return false;
   ArrayInitialize(flat_centroids,0.0f);
   ArrayInitialize(flat_used,0.0f);
   ArrayInitialize(flat_actions,0.0f);
   ArrayInitialize(flat_action_used,0.0f);
   ArrayInitialize(flat_action_mean_reward,0.0f);
   ArrayInitialize(flat_action_hit_count,0.0f);
   for(uint scenario=0;scenario<iScenarioCapacity;scenario++)
     {
      if(!snapshot[scenario].used)
         continue;
      flat_used[scenario]=1.0f;
      for(uint element=0;element<iScenarioEmbedding;element++)
         flat_centroids[scenario*iScenarioEmbedding+element]=snapshot[scenario].centroid[element];
      for(uint action=0;action<iActionCapacity;action++)
        {
         if(!snapshot[scenario].actions[action].used)
            continue;
         const uint slot=scenario*iActionCapacity+action;
         flat_action_used[slot]=1.0f;
         flat_action_mean_reward[slot]=snapshot[scenario].actions[action].mean_reward;
         flat_action_hit_count[slot]=float(snapshot[scenario].actions[action].hit_count);
         for(uint element=0;element<iActionDimension;element++)
            flat_actions[slot*iActionDimension+element]=snapshot[scenario].actions[action].centroid[element];
        }
     }

   CRAGInferenceSet *candidate=new CRAGInferenceSet();
   if(candidate==NULL)
      return false;
   candidate.opencl=OpenCL;
   candidate.fingerprint=fingerprint;
   candidate.centroids=new CBufferFloat(); candidate.used=new CBufferFloat();
   candidate.scores=new CBufferFloat(); candidate.partial=new CBufferFloat(); candidate.merge=new CBufferFloat();
   candidate.top=new CBufferFloat(); candidate.actions=new CBufferFloat();
   candidate.action_used=new CBufferFloat(); candidate.action_mean_reward=new CBufferFloat();
   candidate.action_hit_count=new CBufferFloat(); candidate.absolute_relevance=new CBufferFloat();
   candidate.tokens=new CBufferFloat();
   if(candidate.centroids==NULL || candidate.used==NULL || candidate.scores==NULL ||
      candidate.partial==NULL || candidate.merge==NULL || candidate.top==NULL || candidate.actions==NULL ||
      candidate.action_used==NULL || candidate.action_mean_reward==NULL ||
      candidate.action_hit_count==NULL || candidate.absolute_relevance==NULL || candidate.tokens==NULL ||
      !candidate.centroids.AssignArray(flat_centroids) || !candidate.centroids.BufferCreate(OpenCL) ||
      !candidate.used.AssignArray(flat_used) || !candidate.used.BufferCreate(OpenCL) ||
      !candidate.actions.AssignArray(flat_actions) || !candidate.actions.BufferCreate(OpenCL) ||
      !candidate.action_used.AssignArray(flat_action_used) || !candidate.action_used.BufferCreate(OpenCL) ||
      !candidate.action_mean_reward.AssignArray(flat_action_mean_reward) ||
      !candidate.action_mean_reward.BufferCreate(OpenCL) ||
      !candidate.action_hit_count.AssignArray(flat_action_hit_count) ||
      !candidate.action_hit_count.BufferCreate(OpenCL) ||
      !candidate.absolute_relevance.BufferInit(1,0.0f) ||
      !candidate.absolute_relevance.BufferCreate(OpenCL) ||
      !candidate.scores.BufferInit(iScenarioCapacity,0.0f) || !candidate.scores.BufferCreate(OpenCL) ||
      !candidate.partial.BufferInit((uint)partial_total,0.0f) || !candidate.partial.BufferCreate(OpenCL) ||
      !candidate.merge.BufferInit((uint)partial_total,0.0f) || !candidate.merge.BufferCreate(OpenCL) ||
      !candidate.top.BufferInit((uint)top_total,0.0f) || !candidate.top.BufferCreate(OpenCL) ||
      !candidate.tokens.BufferInit((uint)token_total,0.0f) || !candidate.tokens.BufferCreate(OpenCL))
     {
      delete candidate;
      return false;
     }
   result=candidate;
   return true;
  }

bool CNeuronRAGMemory::Init(uint numOutputs,uint myIndex,COpenCLMy *open_cl,
                            CLayerDescription &description)
  {
   if(!Configure(description))
      ReturnFalse;
   if(iTopK>UINT_MAX/iActionCapacity)
      ReturnFalse;
   const uint token_count=iTopK*iActionCapacity;
   if(token_count==0 || iTokenWidth>UINT_MAX/token_count)
      ReturnFalse;
   if(!CNeuronBaseOCL::Init(numOutputs,myIndex,open_cl,token_count*iTokenWidth,
                            description.optimization,description.batch))
      ReturnFalse;
   if(ArrayResize(scenarios,(int)iScenarioCapacity)!=(int)iScenarioCapacity)
      ReturnFalse;
   for(uint scenario=0;scenario<iScenarioCapacity;scenario++)
      if(!InitializeScenario(scenarios[scenario],scenario))
         ReturnFalse;
   iScenarioCount=0;
   return Clear();
  }

bool CNeuronRAGMemory::SetOnlineMemorySize(const uint value)
  {
   if(value>=(uint)INT_MAX || (value>0 && value<(uint)ArraySize(pending_records)))
      return false;
   iOnlineMemorySize=value;
   return true;
  }

bool CNeuronRAGMemory::BindNullInference(void)
  {
   SRAGScenarioCentroid empty[];
   if(ArrayResize(empty,(int)iScenarioCapacity)!=(int)iScenarioCapacity)
      return false;
   for(uint scenario=0;scenario<iScenarioCapacity;scenario++)
      if(!InitializeScenario(empty[scenario],scenario))
         return false;
   CRAGInferenceSet *candidate=NULL;
   if(!BuildInferenceSet(empty,0,candidate))
      return false;
   CRAGInferenceSet *previous=m_inference;
   m_inference=candidate;
   if(previous!=NULL)
      delete previous;
   return true;
  }

bool CNeuronRAGMemory::AddCompletedRecord(const float &scenario_embedding[],
                                          const float &action[],
                                          const SRAGTerminalOutcome &terminal)
  {
   const uint limit=(iOnlineMemorySize>0 ? iOnlineMemorySize : iScenarioCapacity);
   const int total=ArraySize(pending_records);
   if(limit==0 || total>=(int)limit || ArraySize(scenario_embedding)!=(int)iScenarioEmbedding ||
      !ValidAction(action) || !ValidTerminal(terminal) ||
      ArrayResize(pending_records,total+1)!=total+1)
      return false;
   SRAGCompletedRecord source;
   if(ArrayResize(source.scenario_embedding,(int)iScenarioEmbedding)!=(int)iScenarioEmbedding ||
      ArrayResize(source.action,(int)iActionDimension)!=(int)iActionDimension)
     {
      ArrayResize(pending_records,total);
      return false;
     }
   ArrayCopy(source.scenario_embedding,scenario_embedding);
   ArrayCopy(source.action,action);
   source.terminal=terminal;
   if(!CopyCompletedRecord(source,pending_records[total]))
     {
      ArrayResize(pending_records,total);
      return false;
     }
   return true;
  }

ulong CNeuronRAGMemory::OpenPending(const float &scenario_embedding[],
                                    const float &action[])
  {
   const int total=ArraySize(pending_facts);
   if(iNextPendingId==0 || iNextPendingId==ULONG_MAX || total>=INT_MAX-1 ||
      ArraySize(scenario_embedding)!=(int)iScenarioEmbedding || !ValidAction(action))
      return 0;
   const ulong pending_id=iNextPendingId;
   SRAGPendingFact source;
   source.id=pending_id;
   if(ArrayResize(source.scenario_embedding,(int)iScenarioEmbedding)!=(int)iScenarioEmbedding ||
      ArrayResize(source.events,1)!=1 ||
      ArrayResize(source.events[0].action,(int)iActionDimension)!=(int)iActionDimension)
      return 0;
   ArrayCopy(source.scenario_embedding,scenario_embedding);
   ArrayCopy(source.events[0].action,action);
   source.events[0].confirmed=false;
   source.events[0].deal=0;
   source.events[0].position=0;

   SRAGPendingFact candidate_facts[];
   if(ArrayResize(candidate_facts,total+1)!=total+1)
      return 0;
   for(int fact=0;fact<total;fact++)
      if(!CopyPendingFact(pending_facts[fact],candidate_facts[fact]))
         return 0;
   if(!CopyPendingFact(source,candidate_facts[total]))
      return 0;
   ArraySwap(pending_facts,candidate_facts);
   iNextPendingId++;
   return pending_id;
  }

bool CNeuronRAGMemory::ConfirmPendingExecution(const ulong pending_id,
                                               const ulong deal,
                                               const ulong position)
  {
   const int fact=FindPendingFact(pending_id);
   if(fact<0 || deal==0 || position==0)
      return false;
   const int event=ArraySize(pending_facts[fact].events)-1;
   if(event<0 || pending_facts[fact].events[event].confirmed)
      return false;
   pending_facts[fact].events[event].deal=deal;
   pending_facts[fact].events[event].position=position;
   pending_facts[fact].events[event].confirmed=true;
   return true;
  }

bool CNeuronRAGMemory::CorrectPending(const ulong pending_id,const float &action[])
  {
   const int fact=FindPendingFact(pending_id);
   if(fact<0 || !ValidAction(action))
      return false;
   const int event_count=ArraySize(pending_facts[fact].events);
   if(event_count<=0 || event_count>=INT_MAX ||
      !pending_facts[fact].events[event_count-1].confirmed)
      return false;

   SRAGPendingFact candidate_facts[];
   const int total=ArraySize(pending_facts);
   if(ArrayResize(candidate_facts,total)!=total)
      return false;
   for(int source=0;source<total;source++)
      if(!CopyPendingFact(pending_facts[source],candidate_facts[source]))
         return false;
   if(ArrayResize(candidate_facts[fact].events,event_count+1)!=event_count+1 ||
      ArrayResize(candidate_facts[fact].events[event_count].action,
                  (int)iActionDimension)!=(int)iActionDimension)
      return false;
   ArrayCopy(candidate_facts[fact].events[event_count].action,action);
   candidate_facts[fact].events[event_count].confirmed=false;
   candidate_facts[fact].events[event_count].deal=0;
   candidate_facts[fact].events[event_count].position=0;
   ArraySwap(pending_facts,candidate_facts);
   return true;
  }

bool CNeuronRAGMemory::ConfirmPendingCorrection(const ulong pending_id,const ulong position)
  {
   const int fact=FindPendingFact(pending_id);
   if(fact<0 || position==0)
      return false;
   const int event_count=ArraySize(pending_facts[fact].events);
   const int event=event_count-1;
   // A correction has no terminal deal. It is valid only after the opening
   // event is confirmed for the same live position.
   if(event_count<2 || event<0 || pending_facts[fact].events[event].confirmed ||
      !pending_facts[fact].events[0].confirmed ||
      pending_facts[fact].events[0].position!=position)
      return false;
   pending_facts[fact].events[event].deal=0;
   pending_facts[fact].events[event].position=position;
   pending_facts[fact].events[event].confirmed=true;
   return true;
  }

bool CNeuronRAGMemory::CancelPendingCorrection(const ulong pending_id)
  {
   const int fact=FindPendingFact(pending_id);
   if(fact<0)
      return false;
   const int event_count=ArraySize(pending_facts[fact].events);
   if(event_count<2 || pending_facts[fact].events[event_count-1].confirmed)
      return false;
   SRAGPendingFact candidate_facts[];
   const int total=ArraySize(pending_facts);
   if(ArrayResize(candidate_facts,total)!=total)
      return false;
   for(int source=0;source<total;source++)
      if(!CopyPendingFact(pending_facts[source],candidate_facts[source]))
         return false;
   if(ArrayResize(candidate_facts[fact].events,event_count-1)!=event_count-1)
      return false;
   ArraySwap(pending_facts,candidate_facts);
   return true;
  }

bool CNeuronRAGMemory::ClosePending(const ulong pending_id,
                                    const SRAGTerminalOutcome &terminal)
  {
   const int fact=FindPendingFact(pending_id);
   if(fact<0 || !ValidTerminal(terminal))
      return false;
   int confirmed_count=0;
   for(int event=0;event<ArraySize(pending_facts[fact].events);event++)
      if(pending_facts[fact].events[event].confirmed)
         confirmed_count++;
   const int current=ArraySize(pending_records);
   const ulong limit=(iOnlineMemorySize>0 ? iOnlineMemorySize : iScenarioCapacity);
   if(confirmed_count<=0 || current<0 ||
      (ulong)current+(ulong)confirmed_count>limit ||
      (ulong)current+(ulong)confirmed_count>(ulong)INT_MAX)
      return false;

   SRAGPendingFact remaining_facts[];
   if(!BuildRemainingPendingFacts(fact,remaining_facts))
      return false;
   SRAGCompletedRecord candidate_records[];
   const int candidate_count=current+confirmed_count;
   if(ArrayResize(candidate_records,candidate_count)!=candidate_count)
      return false;
   for(int record=0;record<current;record++)
      if(!CopyCompletedRecord(pending_records[record],candidate_records[record]))
         return false;
   int destination=current;
   for(int event=0;event<ArraySize(pending_facts[fact].events);event++)
     {
      if(!pending_facts[fact].events[event].confirmed)
         continue;
      SRAGCompletedRecord source;
      if(ArrayResize(source.scenario_embedding,(int)iScenarioEmbedding)!=(int)iScenarioEmbedding ||
         ArrayResize(source.action,(int)iActionDimension)!=(int)iActionDimension)
         return false;
      ArrayCopy(source.scenario_embedding,pending_facts[fact].scenario_embedding);
      ArrayCopy(source.action,pending_facts[fact].events[event].action);
      source.terminal=terminal;
      if(!CopyCompletedRecord(source,candidate_records[destination]))
         return false;
      destination++;
     }
   ArraySwap(pending_records,candidate_records);
   ArraySwap(pending_facts,remaining_facts);
   return true;
  }

bool CNeuronRAGMemory::CancelPending(const ulong pending_id)
  {
   const int fact=FindPendingFact(pending_id);
   if(fact<0)
      return false;
   for(int event=0;event<ArraySize(pending_facts[fact].events);event++)
      if(pending_facts[fact].events[event].confirmed)
         return false;
   SRAGPendingFact remaining_facts[];
   if(!BuildRemainingPendingFacts(fact,remaining_facts))
      return false;
   ArraySwap(pending_facts,remaining_facts);
   return true;
  }

bool CNeuronRAGMemory::Publish(const bool episode_closed)
  {
   const uint pending=PendingRecordCount();
   if(pending==0)
      return true;
   if(!episode_closed && (iOnlineMemorySize==0 || pending<iOnlineMemorySize))
      return true;
   SRAGScenarioCentroid candidate_snapshot[];
   uint candidate_count=iScenarioCount;
   if(!CopySnapshot(scenarios,candidate_snapshot))
      return false;
   for(uint record=0;record<pending;record++)
      if(!ApplyCompletedRecordToSnapshot(candidate_snapshot,candidate_count,
                                         iScenarioCapacity+record,
                                         pending_records[record].scenario_embedding,
                                         pending_records[record].action,
                                          pending_records[record].terminal))
         return false;
   if(!ConsolidateActionCandidates(candidate_snapshot))
      return false;
   const ulong fingerprint=SnapshotFingerprint(candidate_snapshot,candidate_count);
   CRAGInferenceSet *candidate=NULL;
   if(!BuildInferenceSet(candidate_snapshot,fingerprint,candidate))
      return false;
   ArraySwap(scenarios,candidate_snapshot);
   iScenarioCount=candidate_count;
   CRAGInferenceSet *previous=m_inference;
   m_inference=candidate;
   if(previous!=NULL)
      delete previous;
   ArrayResize(pending_records,0);
   iPublicationGeneration++;
   return true;
  }

bool CNeuronRAGMemory::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   return Retrieve(NeuronOCL==NULL ? NULL : NeuronOCL.getOutput());
  }

bool CNeuronRAGMemory::Clear(void)
  {
   if(!CNeuronBaseOCL::Clear())
      return false;
   if(m_inference==NULL || m_inference.tokens==NULL)
      return true;
   if(!m_inference.tokens.BufferInit(iTopK*iActionCapacity*iTokenWidth,0.0f))
      return false;
   return m_inference.tokens.BufferCreate(OpenCL);
  }

void CNeuronRAGMemory::SetOpenCL(COpenCLMy *obj)
  {
   const bool was_bound=(m_inference!=NULL);
   const ulong fingerprint=(was_bound ? m_inference.fingerprint : 0);
   if(m_inference!=NULL)
      delete m_inference;
   m_inference=NULL;
   cNearestCentroids.BufferFree();
   cNearestUsed.BufferFree();
   cNearestResult.BufferFree();
   CNeuronBaseOCL::SetOpenCL(obj);
   if(obj==NULL || !was_bound)
      return;
   if(fingerprint==0)
      BindNullInference();
   else
     {
      CRAGInferenceSet *candidate=NULL;
      if(BuildInferenceSet(scenarios,fingerprint,candidate))
         m_inference=candidate;
     }
  }

bool CNeuronRAGMemory::HasBytes(const int file_handle,const ulong bytes) const
  {
   const ulong position=(ulong)FileTell(file_handle);
   const ulong size=(ulong)FileSize(file_handle);
   return (position<=size && bytes<=size-position);
  }

bool CNeuronRAGMemory::WriteUInt(const int file_handle,const uint value) const
  {
   return (FileWriteInteger(file_handle,(int)value,INT_VALUE)==sizeof(int));
  }

bool CNeuronRAGMemory::WriteULong(const int file_handle,const ulong value) const
  {
   return (FileWriteLong(file_handle,(long)value)==sizeof(long));
  }

bool CNeuronRAGMemory::WriteFloat(const int file_handle,const float value) const
  {
   return (MathIsValidNumber(value) && FileWriteFloat(file_handle,value)==sizeof(float));
  }

bool CNeuronRAGMemory::ReadUInt(const int file_handle,uint &value) const
  {
   if(!HasBytes(file_handle,sizeof(int)) || FileIsEnding(file_handle))
      return false;
   value=(uint)FileReadInteger(file_handle,INT_VALUE);
   return true;
  }

bool CNeuronRAGMemory::ReadULong(const int file_handle,ulong &value) const
  {
   if(!HasBytes(file_handle,sizeof(long)) || FileIsEnding(file_handle))
      return false;
   value=(ulong)FileReadLong(file_handle);
   return true;
  }

bool CNeuronRAGMemory::ReadFloat(const int file_handle,float &value) const
  {
   if(!HasBytes(file_handle,sizeof(float)) || FileIsEnding(file_handle))
      return false;
   value=FileReadFloat(file_handle);
   return MathIsValidNumber(value);
  }

bool CNeuronRAGMemory::SnapshotPayloadBytes(const uint scenario_embedding,
                                            const uint action_dimension,
                                            const uint scenario_capacity,
                                            const uint action_capacity,
                                            ulong &bytes) const
  {
   const ulong maximum=ulong(-1);
   if(scenario_embedding>(maximum-16)/sizeof(float) ||
      action_dimension>(maximum-44)/sizeof(float))
      return false;
   const ulong action_bytes=44+(ulong)action_dimension*sizeof(float);
   if(action_capacity>maximum/action_bytes)
      return false;
   const ulong all_actions=(ulong)action_capacity*action_bytes;
   const ulong scenario_prefix=16+(ulong)scenario_embedding*sizeof(float);
   if(all_actions>maximum-scenario_prefix)
      return false;
   const ulong scenario_bytes=scenario_prefix+all_actions;
   if(scenario_capacity>maximum/scenario_bytes)
      return false;
   bytes=(ulong)scenario_capacity*scenario_bytes;
   return true;
  }

bool CNeuronRAGMemory::WritePublishedSnapshot(const int file_handle,const ulong fingerprint) const
  {
   const uint magic=0x52414731;
   const uint format=3;
   ulong payload_bytes=0;
   if(!ValidateSnapshot(scenarios,iScenarioCount,iScenarioEmbedding,iActionDimension,
                        iScenarioCapacity,iActionCapacity) ||
      !SnapshotPayloadBytes(iScenarioEmbedding,iActionDimension,iScenarioCapacity,
                            iActionCapacity,payload_bytes) ||
      !WriteUInt(file_handle,magic) || !WriteUInt(file_handle,format) ||
      !WriteUInt(file_handle,iScenarioEmbedding) || !WriteUInt(file_handle,iActionDimension) ||
      !WriteUInt(file_handle,iTokenWidth) || !WriteUInt(file_handle,iScenarioCapacity) ||
      !WriteUInt(file_handle,iActionCapacity) || !WriteUInt(file_handle,iTopK) ||
      !WriteUInt(file_handle,iOnlineMemorySize) ||
      !WriteUInt(file_handle,iScenarioCount) || !WriteULong(file_handle,fingerprint) ||
      !WriteULong(file_handle,payload_bytes))
      return false;
   for(uint scenario=0;scenario<iScenarioCapacity;scenario++)
     {
      if(!WriteUInt(file_handle,(scenarios[scenario].used ? 1 : 0)) ||
         !WriteUInt(file_handle,scenarios[scenario].scenario_id) ||
         !WriteULong(file_handle,scenarios[scenario].hit_count))
         return false;
      for(uint element=0;element<iScenarioEmbedding;element++)
         if(!WriteFloat(file_handle,scenarios[scenario].centroid[element]))
            return false;
      for(uint action=0;action<iActionCapacity;action++)
        {
         if(!WriteUInt(file_handle,(scenarios[scenario].actions[action].used ? 1 : 0)) ||
            !WriteUInt(file_handle,scenarios[scenario].actions[action].scenario_id) ||
            !WriteULong(file_handle,scenarios[scenario].actions[action].hit_count) ||
            !WriteFloat(file_handle,scenarios[scenario].actions[action].mean_reward) ||
            !WriteFloat(file_handle,scenarios[scenario].actions[action].mean_terminal.relative_return) ||
            !WriteFloat(file_handle,scenarios[scenario].actions[action].mean_terminal.max_drawdown) ||
            !WriteFloat(file_handle,scenarios[scenario].actions[action].mean_terminal.costs) ||
            !WriteFloat(file_handle,scenarios[scenario].actions[action].mean_terminal.risk_usage) ||
            !WriteFloat(file_handle,scenarios[scenario].actions[action].mean_terminal.duration) ||
            !WriteFloat(file_handle,scenarios[scenario].actions[action].mean_terminal.reward))
            return false;
         for(uint element=0;element<iActionDimension;element++)
            if(!WriteFloat(file_handle,scenarios[scenario].actions[action].centroid[element]))
               return false;
        }
     }
   return true;
  }

bool CNeuronRAGMemory::ReadPublishedSnapshot(const int file_handle,
                                              SRAGScenarioCentroid &snapshot[],
                                              uint &scenario_count,ulong &fingerprint) const
  {
   uint magic=0,format=0,embedding=0,action_dimension=0,token_width=0,scenario_capacity=0,
        action_capacity=0,top_k=0,online_memory_size=0;
   ulong payload_bytes=0;
   if(!ReadUInt(file_handle,magic) || !ReadUInt(file_handle,format) ||
      !ReadUInt(file_handle,embedding) || !ReadUInt(file_handle,action_dimension) ||
      !ReadUInt(file_handle,token_width) || !ReadUInt(file_handle,scenario_capacity) ||
      !ReadUInt(file_handle,action_capacity) || !ReadUInt(file_handle,top_k) ||
      !ReadUInt(file_handle,online_memory_size) ||
      !ReadUInt(file_handle,scenario_count) || !ReadULong(file_handle,fingerprint) ||
      !ReadULong(file_handle,payload_bytes) || magic!=0x52414731 || format!=3 ||
      embedding!=iScenarioEmbedding || action_dimension!=iActionDimension ||
      token_width!=iTokenWidth || scenario_capacity!=iScenarioCapacity ||
      action_capacity!=iActionCapacity || top_k!=iTopK || online_memory_size!=iOnlineMemorySize ||
      top_k>RAG_TOPK_MAX || scenario_capacity>RAG_FLOAT_INDEX_LIMIT ||
      scenario_count>scenario_capacity || fingerprint==0)
      return false;
   ulong expected=0;
   if(!SnapshotPayloadBytes(embedding,action_dimension,scenario_capacity,action_capacity,expected) ||
      expected!=payload_bytes || !HasBytes(file_handle,payload_bytes) ||
      ArrayResize(snapshot,(int)scenario_capacity)!=(int)scenario_capacity)
      return false;
   const ulong snapshot_end=(ulong)FileTell(file_handle)+payload_bytes;
   for(uint scenario=0;scenario<scenario_capacity;scenario++)
     {
      uint used=0;
      if(!ReadUInt(file_handle,used) || used>1 ||
         !ReadUInt(file_handle,snapshot[scenario].scenario_id) ||
         !ReadULong(file_handle,snapshot[scenario].hit_count) ||
         ArrayResize(snapshot[scenario].centroid,(int)embedding)!=(int)embedding ||
         ArrayResize(snapshot[scenario].actions,(int)action_capacity)!=(int)action_capacity)
         return false;
      snapshot[scenario].used=(used==1);
      for(uint element=0;element<embedding;element++)
         if(!ReadFloat(file_handle,snapshot[scenario].centroid[element]))
            return false;
      for(uint action=0;action<action_capacity;action++)
        {
         uint action_used=0;
         if(!ReadUInt(file_handle,action_used) || action_used>1 ||
            !ReadUInt(file_handle,snapshot[scenario].actions[action].scenario_id) ||
            !ReadULong(file_handle,snapshot[scenario].actions[action].hit_count) ||
            !ReadFloat(file_handle,snapshot[scenario].actions[action].mean_reward) ||
            !ReadFloat(file_handle,snapshot[scenario].actions[action].mean_terminal.relative_return) ||
            !ReadFloat(file_handle,snapshot[scenario].actions[action].mean_terminal.max_drawdown) ||
            !ReadFloat(file_handle,snapshot[scenario].actions[action].mean_terminal.costs) ||
            !ReadFloat(file_handle,snapshot[scenario].actions[action].mean_terminal.risk_usage) ||
            !ReadFloat(file_handle,snapshot[scenario].actions[action].mean_terminal.duration) ||
            !ReadFloat(file_handle,snapshot[scenario].actions[action].mean_terminal.reward) ||
            ArrayResize(snapshot[scenario].actions[action].centroid,(int)action_dimension)!=(int)action_dimension)
            return false;
         snapshot[scenario].actions[action].used=(action_used==1);
         for(uint element=0;element<action_dimension;element++)
            if(!ReadFloat(file_handle,snapshot[scenario].actions[action].centroid[element]))
               return false;
        }
     }
   return ((ulong)FileTell(file_handle)==snapshot_end &&
           snapshot_end==(ulong)FileSize(file_handle) &&
           ValidateSnapshot(snapshot,scenario_count,embedding,action_dimension,
                            scenario_capacity,action_capacity) &&
           SnapshotFingerprint(snapshot,scenario_count)==fingerprint);
  }

bool CNeuronRAGMemory::SavePublishedSnapshot(const string path) const
  {
   if(StringLen(path)==0)
      return false;
   const ulong fingerprint=SnapshotFingerprint(scenarios,iScenarioCount);
   const int file_handle=FileOpen(path,FILE_WRITE|FILE_BIN|FILE_COMMON);
   if(file_handle==INVALID_HANDLE)
      return false;
   const bool result=WritePublishedSnapshot(file_handle,fingerprint);
   if(result)
      FileFlush(file_handle);
   FileClose(file_handle);
   return result;
  }

bool CNeuronRAGMemory::LoadPublishedSnapshot(const string path)
  {
   if(StringLen(path)==0 || OpenCL==NULL)
      return false;
   const int file_handle=FileOpen(path,FILE_READ|FILE_BIN|FILE_COMMON|FILE_SHARE_READ);
   if(file_handle==INVALID_HANDLE)
      return false;
   SRAGScenarioCentroid loaded[];
   uint scenario_count=0;
   ulong fingerprint=0;
   const bool read=ReadPublishedSnapshot(file_handle,loaded,scenario_count,fingerprint);
   const bool exact_end=(FileTell(file_handle)==FileSize(file_handle));
   FileClose(file_handle);
   if(!read || !exact_end)
      return false;
   CRAGInferenceSet *candidate=NULL;
   if(!BuildInferenceSet(loaded,fingerprint,candidate))
      return false;
   if(!CommitSnapshot(loaded,scenario_count))
     {
      delete candidate;
      return false;
     }
   CRAGInferenceSet *previous=m_inference;
   m_inference=candidate;
   if(previous!=NULL)
      delete previous;
   ArrayResize(pending_records,0);
   iPublicationGeneration++;
   return true;
  }

bool CNeuronRAGMemory::BindInferenceSnapshot(const string path)
  {
   return LoadPublishedSnapshot(path);
  }

bool CNeuronRAGMemory::Save(const int file_handle)
  {
   if(!ValidateSnapshot(scenarios,iScenarioCount,iScenarioEmbedding,iActionDimension,
                        iScenarioCapacity,iActionCapacity) ||
      !CNeuronBaseOCL::Save(file_handle))
      ReturnFalse;
   ulong payload_bytes=0;
   if(!SnapshotPayloadBytes(iScenarioEmbedding,iActionDimension,iScenarioCapacity,
                            iActionCapacity,payload_bytes) ||
      !WriteUInt(file_handle,iFormatVersion) ||
      !WriteUInt(file_handle,iScenarioEmbedding) ||
      !WriteUInt(file_handle,iActionDimension) ||
      !WriteUInt(file_handle,iTokenWidth) ||
      !WriteUInt(file_handle,iScenarioCapacity) ||
      !WriteUInt(file_handle,iActionCapacity) ||
      !WriteUInt(file_handle,iTopK) ||
      !WriteUInt(file_handle,iOnlineMemorySize) ||
      !WriteUInt(file_handle,iScenarioCount) ||
      !WriteULong(file_handle,payload_bytes))
      ReturnFalse;
   for(uint scenario=0;scenario<iScenarioCapacity;scenario++)
     {
      if(!WriteUInt(file_handle,(scenarios[scenario].used ? 1 : 0)) ||
         !WriteUInt(file_handle,scenarios[scenario].scenario_id) ||
         !WriteULong(file_handle,scenarios[scenario].hit_count))
         ReturnFalse;
      for(uint element=0;element<iScenarioEmbedding;element++)
         if(!WriteFloat(file_handle,scenarios[scenario].centroid[element]))
            ReturnFalse;
      for(uint action=0;action<iActionCapacity;action++)
        {
         if(!WriteUInt(file_handle,(scenarios[scenario].actions[action].used ? 1 : 0)) ||
            !WriteUInt(file_handle,scenarios[scenario].actions[action].scenario_id) ||
            !WriteULong(file_handle,scenarios[scenario].actions[action].hit_count) ||
            !WriteFloat(file_handle,scenarios[scenario].actions[action].mean_reward) ||
            !WriteFloat(file_handle,scenarios[scenario].actions[action].mean_terminal.relative_return) ||
            !WriteFloat(file_handle,scenarios[scenario].actions[action].mean_terminal.max_drawdown) ||
            !WriteFloat(file_handle,scenarios[scenario].actions[action].mean_terminal.costs) ||
            !WriteFloat(file_handle,scenarios[scenario].actions[action].mean_terminal.risk_usage) ||
            !WriteFloat(file_handle,scenarios[scenario].actions[action].mean_terminal.duration) ||
            !WriteFloat(file_handle,scenarios[scenario].actions[action].mean_terminal.reward))
            ReturnFalse;
         for(uint element=0;element<iActionDimension;element++)
            if(!WriteFloat(file_handle,scenarios[scenario].actions[action].centroid[element]))
               ReturnFalse;
        }
     }
   return true;
  }

bool CNeuronRAGMemory::Load(const int file_handle)
  {
   if(!CNeuronBaseOCL::Load(file_handle))
      ReturnFalse;
   uint format_version=0;
   uint scenario_embedding=0;
   uint action_dimension=0;
   uint token_width=0;
   uint scenario_capacity=0;
   uint action_capacity=0;
   uint top_k=0;
   uint online_memory_size=0;
   uint scenario_count=0;
   ulong payload_bytes=0;
   if(!ReadUInt(file_handle,format_version) || !ReadUInt(file_handle,scenario_embedding) ||
      !ReadUInt(file_handle,action_dimension) || !ReadUInt(file_handle,token_width) ||
      !ReadUInt(file_handle,scenario_capacity) || !ReadUInt(file_handle,action_capacity) ||
      !ReadUInt(file_handle,top_k) || !ReadUInt(file_handle,online_memory_size) ||
      !ReadUInt(file_handle,scenario_count) || !ReadULong(file_handle,payload_bytes) ||
      format_version!=2 || scenario_embedding==0 || action_dimension==0 || token_width==0 ||
      scenario_capacity==0 || action_capacity==0 || top_k==0 ||
      top_k>RAG_TOPK_MAX || scenario_capacity>RAG_FLOAT_INDEX_LIMIT ||
      scenario_capacity>=(uint)INT_MAX || action_capacity>=(uint)INT_MAX ||
      scenario_count>scenario_capacity)
      ReturnFalse;
   ulong expected_payload=0;
   if(!SnapshotPayloadBytes(scenario_embedding,action_dimension,scenario_capacity,
                            action_capacity,expected_payload) || payload_bytes!=expected_payload ||
      !HasBytes(file_handle,payload_bytes))
      ReturnFalse;
   const ulong snapshot_end=(ulong)FileTell(file_handle)+payload_bytes;

   SRAGScenarioCentroid loaded[];
   if(ArrayResize(loaded,(int)scenario_capacity)!=(int)scenario_capacity)
      ReturnFalse;
   for(uint scenario=0;scenario<scenario_capacity;scenario++)
     {
      uint used=0;
      if(!ReadUInt(file_handle,used) || used>1 ||
         !ReadUInt(file_handle,loaded[scenario].scenario_id) ||
         !ReadULong(file_handle,loaded[scenario].hit_count) ||
         ArrayResize(loaded[scenario].centroid,(int)scenario_embedding)!=(int)scenario_embedding ||
         ArrayResize(loaded[scenario].actions,(int)action_capacity)!=(int)action_capacity)
         ReturnFalse;
      loaded[scenario].used=(used==1);
      for(uint element=0;element<scenario_embedding;element++)
         if(!ReadFloat(file_handle,loaded[scenario].centroid[element]))
            ReturnFalse;
      for(uint action=0;action<action_capacity;action++)
        {
         uint action_used=0;
         if(!ReadUInt(file_handle,action_used) || action_used>1 ||
            !ReadUInt(file_handle,loaded[scenario].actions[action].scenario_id) ||
            !ReadULong(file_handle,loaded[scenario].actions[action].hit_count) ||
            !ReadFloat(file_handle,loaded[scenario].actions[action].mean_reward) ||
            !ReadFloat(file_handle,loaded[scenario].actions[action].mean_terminal.relative_return) ||
            !ReadFloat(file_handle,loaded[scenario].actions[action].mean_terminal.max_drawdown) ||
            !ReadFloat(file_handle,loaded[scenario].actions[action].mean_terminal.costs) ||
            !ReadFloat(file_handle,loaded[scenario].actions[action].mean_terminal.risk_usage) ||
            !ReadFloat(file_handle,loaded[scenario].actions[action].mean_terminal.duration) ||
            !ReadFloat(file_handle,loaded[scenario].actions[action].mean_terminal.reward) ||
            ArrayResize(loaded[scenario].actions[action].centroid,(int)action_dimension)!=(int)action_dimension)
            ReturnFalse;
         loaded[scenario].actions[action].used=(action_used==1);
         for(uint element=0;element<action_dimension;element++)
            if(!ReadFloat(file_handle,loaded[scenario].actions[action].centroid[element]))
               ReturnFalse;
        }
     }
   if((ulong)FileTell(file_handle)!=snapshot_end ||
      !ValidateSnapshot(loaded,scenario_count,scenario_embedding,action_dimension,
                        scenario_capacity,action_capacity))
      ReturnFalse;

   const uint old_format_version=iFormatVersion;
   const uint old_scenario_embedding=iScenarioEmbedding;
   const uint old_action_dimension=iActionDimension;
   const uint old_token_width=iTokenWidth;
   const uint old_scenario_capacity=iScenarioCapacity;
   const uint old_action_capacity=iActionCapacity;
   const uint old_top_k=iTopK;
   const uint old_online_memory_size=iOnlineMemorySize;
   iFormatVersion=format_version;
   iScenarioEmbedding=scenario_embedding;
   iActionDimension=action_dimension;
   iTokenWidth=token_width;
   iScenarioCapacity=scenario_capacity;
   iActionCapacity=action_capacity;
   iTopK=top_k;
   iOnlineMemorySize=online_memory_size;
   if(CommitSnapshot(loaded,scenario_count))
      return true;
   iFormatVersion=old_format_version;
   iScenarioEmbedding=old_scenario_embedding;
   iActionDimension=old_action_dimension;
   iTokenWidth=old_token_width;
   iScenarioCapacity=old_scenario_capacity;
   iActionCapacity=old_action_capacity;
   iTopK=old_top_k;
   iOnlineMemorySize=old_online_memory_size;
   return false;
  }

// Generic device gateway.  Callers provide buffers sized from their own
// CLayerDescription; no Expert constant participates in launch geometry.
bool DispatchRAGPipeline(COpenCLMy *opencl,CBufferFloat *embedding,
                         CBufferFloat *centroids,CBufferFloat *used,
                         CBufferFloat *scores,CBufferFloat *partial,CBufferFloat *merge,
                         CBufferFloat *top,CBufferFloat *actions,
                         CBufferFloat *action_used,CBufferFloat *action_mean_reward,
                         CBufferFloat *action_hit_count,CBufferFloat *absolute_relevance,
                         CBufferFloat *tokens,const uint scenario_count,
                         const uint embedding_dimension,const uint action_count,
                         const uint action_dimension,const uint token_width,const uint top_k)
  {
   if(opencl==NULL || embedding==NULL || centroids==NULL || used==NULL ||
      scores==NULL || partial==NULL || merge==NULL || top==NULL || actions==NULL || action_used==NULL || tokens==NULL ||
      action_mean_reward==NULL || action_hit_count==NULL ||
      absolute_relevance==NULL ||
      scenario_count==0 || embedding_dimension==0 || action_count==0 || action_dimension==0 || top_k==0 ||
      action_dimension>INT_MAX-2 || token_width!=action_dimension+2 ||
      scenario_count>INT_MAX || embedding_dimension>INT_MAX || action_count>INT_MAX ||
      token_width>INT_MAX || top_k>INT_MAX || top_k>RAG_TOPK_MAX ||
      scenario_count>RAG_FLOAT_INDEX_LIMIT)
      return false;
   const uint block_size=RAG_TOPK_LOCAL_WIDTH;
   // The staged ping-pong compaction keeps every intermediate buffer bounded
   // by the initial partial image.  TopK must therefore reduce a full block.
   if(top_k>RAG_TOPK_MAX)
      return false;
   const uint partial_count=(scenario_count+block_size-1)/block_size;
   if((ulong)scenario_count>ULONG_MAX/(ulong)embedding_dimension ||
      (ulong)scenario_count>ULONG_MAX/(ulong)action_count)
      return false;
   const ulong centroid_floats=(ulong)scenario_count*(ulong)embedding_dimension;
   const ulong action_slots=(ulong)scenario_count*(ulong)action_count;
   if(action_slots>ULONG_MAX/(ulong)action_dimension || (ulong)top_k>ULONG_MAX/(ulong)action_count)
      return false;
   const ulong action_floats=action_slots*(ulong)action_dimension;
   const ulong top_actions=(ulong)top_k*(ulong)action_count;
    if(top_actions>ULONG_MAX/(ulong)token_width)
       return false;
    const ulong token_floats=top_actions*(ulong)token_width;
    const int embedding_total=embedding.Total();
    const int centroids_total=centroids.Total();
    const int used_total=used.Total();
    const int scores_total=scores.Total();
    const int partial_total=partial.Total();
    const int merge_total=merge.Total();
    const int top_total=top.Total();
    const int actions_total=actions.Total();
    const int action_used_total=action_used.Total();
    const int action_mean_reward_total=action_mean_reward.Total();
    const int action_hit_count_total=action_hit_count.Total();
    const int absolute_relevance_total=absolute_relevance.Total();
    const int tokens_total=tokens.Total();
    if(embedding_total<0 || centroids_total<0 || used_total<0 || scores_total<0 || partial_total<0 || merge_total<0 ||
       top_total<0 || actions_total<0 || action_used_total<0 || action_mean_reward_total<0 ||
       action_hit_count_total<0 || absolute_relevance_total<0 || tokens_total<0)
       return false;
    if(centroid_floats>UINT_MAX || action_slots>UINT_MAX || action_floats>UINT_MAX || token_floats>UINT_MAX ||
       partial_count>UINT_MAX/top_k || partial_count*top_k>UINT_MAX/2 || (ulong)embedding_total<embedding_dimension ||
       (ulong)centroids_total<centroid_floats || (ulong)used_total<scenario_count || (ulong)scores_total<scenario_count ||
       (ulong)partial_total<partial_count*top_k*2 || (ulong)merge_total<partial_count*top_k*2 ||
       (ulong)top_total<top_k*2 || (ulong)actions_total<action_floats ||
       (ulong)action_used_total<action_slots || (ulong)action_mean_reward_total<action_slots ||
       (ulong)action_hit_count_total<action_slots || absolute_relevance_total<1 ||
       (ulong)tokens_total<token_floats)
      return false;
   uint offset[]={0};
   uint score_global[]={scenario_count};
   uint partial_global[]={partial_count*block_size};
   uint partial_local[]={block_size};
   uint gather_global[]={top_k*action_count};
   if(!opencl.SetArgumentBuffer(def_k_RAGScore,def_k_rags_embedding,embedding.GetIndex()) ||
      !opencl.SetArgumentBuffer(def_k_RAGScore,def_k_rags_centroids,centroids.GetIndex()) ||
      !opencl.SetArgumentBuffer(def_k_RAGScore,def_k_rags_used,used.GetIndex()) ||
      !opencl.SetArgumentBuffer(def_k_RAGScore,def_k_rags_scores,scores.GetIndex()) ||
      !opencl.SetArgument(def_k_RAGScore,def_k_rags_scenario_count,scenario_count) ||
      !opencl.SetArgument(def_k_RAGScore,def_k_rags_embedding_dimension,embedding_dimension) ||
      !opencl.Execute(def_k_RAGScore,1,offset,score_global))
      return false;
   if(!opencl.SetArgumentBuffer(def_k_RAGPartial,def_k_ragp_scores,scores.GetIndex()) ||
      !opencl.SetArgumentBuffer(def_k_RAGPartial,def_k_ragp_partial,partial.GetIndex()) ||
      !opencl.SetArgument(def_k_RAGPartial,def_k_ragp_scenario_count,scenario_count) ||
      !opencl.SetArgument(def_k_RAGPartial,def_k_ragp_block_size,block_size) ||
      !opencl.SetArgument(def_k_RAGPartial,def_k_ragp_top_k,top_k) ||
      !opencl.Execute(def_k_RAGPartial,1,offset,partial_global,partial_local))
      return false;
   uint merge_count=partial_count*top_k;
   CBufferFloat *source=partial;
   CBufferFloat *destination=merge;
   while(merge_count>block_size)
     {
      const uint merge_blocks=(merge_count+block_size-1)/block_size;
      if(merge_blocks>UINT_MAX/top_k)
         return false;
      const uint next_count=merge_blocks*top_k;
      uint merge_global[]={merge_blocks*block_size};
      uint merge_local[]={block_size};
      if(!opencl.SetArgumentBuffer(def_k_RAGMerge,def_k_ragm_source,source.GetIndex()) ||
         !opencl.SetArgumentBuffer(def_k_RAGMerge,def_k_ragm_destination,destination.GetIndex()) ||
         !opencl.SetArgument(def_k_RAGMerge,def_k_ragm_candidate_count,merge_count) ||
         !opencl.SetArgument(def_k_RAGMerge,def_k_ragm_block_size,block_size) ||
         !opencl.SetArgument(def_k_RAGMerge,def_k_ragm_top_k,top_k) ||
         !opencl.Execute(def_k_RAGMerge,1,offset,merge_global,merge_local))
         return false;
      merge_count=next_count;
      CBufferFloat *swap=source;
      source=destination;
      destination=swap;
     }
   uint merge_global[]={block_size};
   uint merge_local[]={block_size};
   if(!opencl.SetArgumentBuffer(def_k_RAGMerge,def_k_ragm_source,source.GetIndex()) ||
      !opencl.SetArgumentBuffer(def_k_RAGMerge,def_k_ragm_destination,top.GetIndex()) ||
      !opencl.SetArgument(def_k_RAGMerge,def_k_ragm_candidate_count,merge_count) ||
      !opencl.SetArgument(def_k_RAGMerge,def_k_ragm_block_size,block_size) ||
      !opencl.SetArgument(def_k_RAGMerge,def_k_ragm_top_k,top_k) ||
      !opencl.Execute(def_k_RAGMerge,1,offset,merge_global,merge_local))
      return false;
   if(!opencl.SetArgumentBuffer(def_k_RAGGather,def_k_ragg_top,top.GetIndex()) ||
      !opencl.SetArgumentBuffer(def_k_RAGGather,def_k_ragg_actions,actions.GetIndex()) ||
      !opencl.SetArgumentBuffer(def_k_RAGGather,def_k_ragg_tokens,tokens.GetIndex()) ||
      !opencl.SetArgument(def_k_RAGGather,def_k_ragg_scenario_count,scenario_count) ||
      !opencl.SetArgument(def_k_RAGGather,def_k_ragg_action_count,action_count) ||
      !opencl.SetArgument(def_k_RAGGather,def_k_ragg_action_dimension,action_dimension) ||
      !opencl.SetArgument(def_k_RAGGather,def_k_ragg_token_width,token_width) ||
      !opencl.SetArgument(def_k_RAGGather,def_k_ragg_top_k,top_k) ||
      !opencl.SetArgumentBuffer(def_k_RAGGather,def_k_ragg_action_used,action_used.GetIndex()) ||
      !opencl.SetArgumentBuffer(def_k_RAGGather,def_k_ragg_action_mean_reward,action_mean_reward.GetIndex()) ||
      !opencl.SetArgumentBuffer(def_k_RAGGather,def_k_ragg_action_hit_count,action_hit_count.GetIndex()) ||
      !opencl.SetArgumentBuffer(def_k_RAGGather,def_k_ragg_absolute_relevance,absolute_relevance.GetIndex()) ||
      !opencl.Execute(def_k_RAGGather,1,offset,gather_global))
      return false;
   return true;
  }

bool CNeuronRAGMemory::Retrieve(CNet *scenario_model,const uint layer)
  {
   if(!scenario_model)
      return false;
   CBufferFloat *scenario_embedding=NULL;
   if(!scenario_model.GetLayerOutputDevice(layer,scenario_embedding))
      return false;
   return Retrieve(scenario_embedding);
  }

bool CNeuronRAGMemory::Retrieve(CBufferFloat *scenario_embedding)
  {
   if(m_inference==NULL || m_inference.opencl==NULL || m_inference.opencl!=OpenCL ||
      scenario_embedding==NULL || scenario_embedding.GetOpenCL()!=OpenCL ||
      m_inference.centroids==NULL || m_inference.used==NULL || m_inference.scores==NULL ||
      m_inference.partial==NULL || m_inference.merge==NULL || m_inference.top==NULL || m_inference.actions==NULL ||
      m_inference.action_used==NULL || m_inference.action_mean_reward==NULL ||
      m_inference.action_hit_count==NULL || m_inference.absolute_relevance==NULL ||
      m_inference.tokens==NULL)
      return false;
   return DispatchRAGPipeline(OpenCL,scenario_embedding,m_inference.centroids,m_inference.used,
                              m_inference.scores,m_inference.partial,m_inference.merge,m_inference.top,
                              m_inference.actions,m_inference.action_used,m_inference.action_mean_reward,
                              m_inference.action_hit_count,m_inference.absolute_relevance,m_inference.tokens,
                              iScenarioCapacity,iScenarioEmbedding,iActionCapacity,
                              iActionDimension,iTokenWidth,iTopK);
  }

bool CNeuronRAGMemory::ScenarioCentroid(const uint scenario_id,float &destination[]) const
  {
   if(scenario_id>=iScenarioCapacity || !scenarios[scenario_id].used ||
      ArraySize(destination)<(int)iScenarioEmbedding)
      return false;
   ArrayCopy(destination,scenarios[scenario_id].centroid);
   return true;
  }

bool CNeuronRAGMemory::ActionCentroid(const uint scenario_id,const uint action_id,
                                      float &destination[]) const
  {
   if(scenario_id>=iScenarioCapacity || action_id>=iActionCapacity ||
      !scenarios[scenario_id].actions[action_id].used ||
      ArraySize(destination)<(int)iActionDimension)
      return false;
   ArrayCopy(destination,scenarios[scenario_id].actions[action_id].centroid);
   return true;
  }

ulong CNeuronRAGMemory::ScenarioHitCount(const uint scenario_id) const
  {
   return (scenario_id<iScenarioCapacity && scenarios[scenario_id].used ?
           scenarios[scenario_id].hit_count : 0);
  }

ulong CNeuronRAGMemory::ActionHitCount(const uint scenario_id,const uint action_id) const
  {
   return (scenario_id<iScenarioCapacity && action_id<iActionCapacity &&
           scenarios[scenario_id].actions[action_id].used ?
           scenarios[scenario_id].actions[action_id].hit_count : 0);
  }

CLayerDescription* CNeuronRAGMemory::GetLayerInfo(void)
  {
   CLayerDescription *result=CNeuronBaseOCL::GetLayerInfo();
   if(result==NULL)
      return NULL;
   result.type=defNeuronRAGMemory;
   result.count=iTokenWidth;
   result.window=iScenarioEmbedding;
   result.window_out=iActionDimension;
   result.layers=iTopK;
   ArrayResize(result.units,1);
   ArrayResize(result.heads,1);
   result.units[0]=iScenarioCapacity;
   result.heads[0]=iActionCapacity;
   return result;
  }

#endif // NEURONET_RAG_MEMORY_MQH
