// Copyright (c) 2021, salesforce.com, inc. // All rights reserved. // SPDX-License-Identifier: BSD-3-Clause // For full license text, see the LICENSE file in the repo root // or https://opensource.org/licenses/BSD-3-Clause // Real Business Chain implementation in CUDA C #include #include typedef enum { kConsumerType, kFirmType, kGovernmentType, } AgentType; const size_t kBatchSize = M_BATCHSIZE; const size_t kNumConsumers = M_NUMCONSUMERS; const bool kCountFirmReward = M_COUNTFIRMREWARD; const size_t kNumFirms = M_NUMFIRMS; const size_t kNumGovts = M_NUMGOVERNMENTS; const float kMaxTime = M_MAXTIME; const float kCrraParam = M_CRRA_PARAM; const float kInterestRate = M_INTERESTRATE; const size_t kNumAgents = kNumConsumers + kNumFirms + kNumGovts; const bool kIncentivizeFirmActivity = M_SHOULDBOOSTFIRMREWARD; const float kFirmBoostRewardFactor = M_BOOSTFIRMREWARDFACTOR; const bool kUseImporter = M_USEIMPORTER; const float kImporterPrice = M_IMPORTERPRICE; const float kImporterQuantity = M_IMPORTERQUANTITY; const float kLaborFloor = M_LABORFLOOR; // Global state = const size_t kNumPrices = kNumFirms; // - prices, const size_t kNumWages = kNumFirms; // - wages, const size_t kNumInventories = kNumFirms; // - stocks, const size_t kNumOverdemandFlags = kNumFirms; // - good overdemanded flag, const size_t kNumCorporateTaxes = kNumGovts; // - corporate tax rate const size_t kNumIncomeTaxes = kNumGovts; // - income tax rate const size_t kNumTimeDimensions = 1; // - time step const size_t kGlobalStateSize = kNumPrices + kNumWages + kNumInventories + kNumOverdemandFlags + kNumCorporateTaxes + kNumIncomeTaxes + kNumTimeDimensions; const size_t kIdxPricesOffset = 0; const size_t kIdxWagesOffset = kNumPrices; const size_t kIdxStockOffset = kIdxWagesOffset + kNumInventories; const size_t kIdxOverdemandOffset = kIdxStockOffset + kNumOverdemandFlags; const size_t kIdxIncomeTaxOffset = kGlobalStateSize - 3; const size_t kIdxCorporateTaxOffset = kGlobalStateSize - 2; const size_t kIdxTimeOffset = kGlobalStateSize - 1; // Consumer actions: consume, work, choose which firm to work for const size_t kActionSizeConsumer = kNumFirms + 1 + 1; // add budget and theta const size_t kStateSizeConsumer = kGlobalStateSize + 1 + 1; const size_t kIdxConsumerBudgetOffset = 0; // offset from agent-specific state part of array const size_t kIdxConsumerThetaOffset = 1; // UNUSED for consumer. Actions are floats. // __constant__ float cs_index_to_action[num_actions_consumer * // kActionSizeConsumer]; const size_t kActionSizeConsumer = kNumFirms + // kNumFirms; // consume + work /*const size_t num_actions_consumer = NUMACTIONSkConsumerType; // depends on discretization*/ // Firm actions: set wage, set price, invest in capital const size_t kActionSizeFirm = 3; // Number of actions depends on discretization of continuous action space. const size_t kNumActionsFirm = M_NUMACTIONSFIRM; // budget, capital, production alpha, and one-hot firm ID const size_t kStateSizeFirm = kGlobalStateSize + 1 + 1 + 1 + kNumFirms; // offset from agent-specific state part of array const size_t kIdxFirmBudgetOffset = 0; const size_t kIdxFirmCapitalOffset = 1; const size_t kIdxFirmAlphaOffset = 2; const size_t kIdxFirmOnehotOffset = 3; // Constant memory available from ALL threads. // See https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#constant __constant__ float kFirmIndexToAction[kNumActionsFirm * kActionSizeFirm]; // Corporate + income tax rates const size_t kGovtActionSize = 2; const size_t kNumActionsGovernment = M_NUMACTIONSGOVERNMENT; const size_t kGovtStateSize = kGlobalStateSize; __constant__ float kGovernmentIndexToAction[kNumActionsGovernment * kGovtActionSize]; // One RNG state for each thread. Each thread is assigned to an agent in an env. __device__ curandState_t *rng_state_arr[kBatchSize * kNumAgents]; // not sure best way to do this // Offsets into action vectors const size_t kIdxConsumerDemandedOffset = 0; const size_t kIdxConsumerWorkedOffset = kNumFirms; const size_t kIdxConsumerWhichFirmOffset = kNumFirms + 1; // currently 1 govt const size_t kIdxThisThreadGovtId = 0; extern "C" { // ------------------ // CUDA C Utilities // ------------------ __device__ void CopyFloatArraySlice(float *start_point, int num_elems, float *destination) { for (int i = 0; i < num_elems; i++) { destination[i] = start_point[i]; } } __device__ void CopyIntArraySlice(int *start_point, int num_elems, float *destination) { for (int i = 0; i < num_elems; i++) { destination[i] = start_point[i]; } } // unfortunately, you can't do templates with extern "C" linkage required for // CUDA, so we have to define different functions for each case. __device__ int *GetPointerFromMultiIndexFor3DIntTensor(int *array, const dim3 &sizes, const dim3 &index) { unsigned int flat_index = index.z + index.y * (sizes.z) + index.x * (sizes.z * sizes.y); return &(array[flat_index]); } __device__ float *GetPointerFromMultiIndexFor3DFloatTensor(float *array, const dim3 &sizes, const dim3 &index) { unsigned int flat_index = index.z + index.y * (sizes.z) + index.x * (sizes.z * sizes.y); return &(array[flat_index]); } __device__ float * GetPointerFromMultiIndexFor4DTensor(float *array, const size_t *sizes, const size_t *multi_index) { // don't use this for arrays that arne't exactly size 4!!! unsigned int flat_index = multi_index[3] + multi_index[2] * sizes[3] + multi_index[1] * sizes[3] * sizes[2] + multi_index[0] * sizes[3] * sizes[2] * sizes[1]; return &(array[flat_index]); } __global__ void CudaInitKernel(int seed) { // we want to reset random seeds for all firms and consumers int tidx = threadIdx.x; const int kThisThreadGlobalArrayIdx = blockIdx.x * kNumAgents + threadIdx.x; if (tidx < kNumAgents) { curandState_t *s = new curandState_t; if (s != 0) { curand_init(seed, kThisThreadGlobalArrayIdx, 0, s); } rng_state_arr[kThisThreadGlobalArrayIdx] = s; } } __global__ void CudaFreeRand() { int tidx = threadIdx.x; const int kThisThreadGlobalArrayIdx = blockIdx.x * kNumAgents + threadIdx.x; if (tidx < kNumAgents) { curandState_t *s = rng_state_arr[kThisThreadGlobalArrayIdx]; delete s; } } __device__ int SearchIndex(float *distr, float p, int l, int r) { int mid; int left = l; int right = r; while (left <= right) { mid = left + (right - left) / 2; if (distr[mid] == p) { return mid; } else if (distr[mid] < p) { left = mid + 1; } else { right = mid - 1; } } return left > r ? r : left; } // -------------------- // Simulation Utilities // -------------------- __device__ AgentType GetAgentType(const int agent_id) { if (agent_id < kNumConsumers) { return kConsumerType; } else if (agent_id < (kNumConsumers + kNumFirms)) { return kFirmType; } else { return kGovernmentType; } } __device__ float GetCRRAUtil(float consumption, float crra_param) { return (powf(consumption + 1, 1.0 - crra_param) - 1.0) / (1.0 - crra_param); } __global__ void CudaResetEnv(float *cs_state_arr, float *fm_state_arr, float *govt_state_arr, float *cs_state_ckpt_arr, float *fm_state_ckpt_arr, float *govt_state_ckpt_arr, float theta_anneal_factor) { /* Resets the environment by writing the initial state (checkpoint) into the state array for this agent (thread). */ const int kBlockId = blockIdx.x; const int kWithinBlockAgentId = threadIdx.x; if (kWithinBlockAgentId >= kNumAgents) { return; } AgentType ThisThreadAgentType = GetAgentType(kWithinBlockAgentId); float *state_arr; float *ckpt_arr; dim3 my_state_shape, my_state_idx; size_t my_state_size; if (ThisThreadAgentType == kConsumerType) { // This thread/agent is a consumer. my_state_size = kStateSizeConsumer; my_state_shape = {kBatchSize, kNumConsumers, kStateSizeConsumer}; my_state_idx = {kBlockId, kWithinBlockAgentId, 0}; state_arr = cs_state_arr; ckpt_arr = cs_state_ckpt_arr; } else if (ThisThreadAgentType == kFirmType) { // This thread/agent is a firm. my_state_size = kStateSizeFirm; my_state_shape = {kBatchSize, kNumFirms, kStateSizeFirm}; my_state_idx = {kBlockId, (unsigned int)(kWithinBlockAgentId - kNumConsumers), 0}; state_arr = fm_state_arr; ckpt_arr = fm_state_ckpt_arr; } else { // This thread/agent is government. my_state_size = kGovtStateSize; my_state_shape = {kBatchSize, kNumGovts, kGovtStateSize}; my_state_idx = { kBlockId, (unsigned int)(kWithinBlockAgentId - kNumConsumers - kNumFirms), 0}; state_arr = govt_state_arr; ckpt_arr = govt_state_ckpt_arr; } float *my_state_arr = GetPointerFromMultiIndexFor3DFloatTensor( state_arr, my_state_shape, my_state_idx); float *my_ckpt_ptr = GetPointerFromMultiIndexFor3DFloatTensor( ckpt_arr, my_state_shape, my_state_idx); CopyFloatArraySlice(my_ckpt_ptr, my_state_size, my_state_arr); // anneal theta if (ThisThreadAgentType == kConsumerType) { my_state_arr[kGlobalStateSize + kIdxConsumerThetaOffset] *= theta_anneal_factor; } } __device__ void GetAction(float *action_arr, float *index_to_action_arr, int index, int agent_idx, int agent_action_size) { // it needs to be possible to call this for either agents or firms // Note: each thread is an agent. for (int i = 0; i < agent_action_size; i++) { action_arr[agent_idx * agent_action_size + i] = index_to_action_arr[index * agent_action_size + i]; } } __global__ void CudaSampleFirmAndGovernmentActions( float *fm_distr, int *fm_action_indices_arr, float *fm_actions_arr, float *govt_distr, int *govt_action_indices_arr, float *govt_actions_arr) { // Samples actions for firms and governments. Consumer actions are sampled in // Pytorch... const int kWithinBlockAgentId = threadIdx.x; // Unused threads should not do anything. if (threadIdx.x >= kNumAgents) { return; } AgentType ThisThreadAgentType = GetAgentType(kWithinBlockAgentId); // Index into rand states array int kThisThreadGlobalArrayIdx = blockIdx.x * kNumAgents + threadIdx.x; curandState_t rng_state = *rng_state_arr[kThisThreadGlobalArrayIdx]; *rng_state_arr[kThisThreadGlobalArrayIdx] = rng_state; // float cs_cum_dist[num_actions_consumer]; float fm_cum_dist[kNumActionsFirm]; float govt_cum_dist[kNumActionsGovernment]; float *my_cumul_dist; float *my_dist; int *my_indices; float *my_actions; float *index_to_action; int this_thread_global_array_idx; size_t my_num_actions; int my_action_size; // Consumers have multiple action heads, hence sampling is more complicated. if (ThisThreadAgentType == kConsumerType) { return; } else if (ThisThreadAgentType == kFirmType) { // on firm thread my_cumul_dist = fm_cum_dist; my_dist = fm_distr; my_indices = fm_action_indices_arr; my_actions = fm_actions_arr; my_num_actions = kNumActionsFirm; index_to_action = kFirmIndexToAction; my_action_size = kActionSizeFirm; this_thread_global_array_idx = (blockIdx.x * kNumFirms) + (threadIdx.x - kNumConsumers); } else { my_cumul_dist = govt_cum_dist; my_dist = govt_distr; my_indices = govt_action_indices_arr; my_actions = govt_actions_arr; my_num_actions = kNumActionsGovernment; index_to_action = kGovernmentIndexToAction; my_action_size = kGovtActionSize; this_thread_global_array_idx = (blockIdx.x * kNumGovts) + (threadIdx.x - kNumConsumers - kNumFirms); } // Compute CDF my_cumul_dist[0] = my_dist[this_thread_global_array_idx * my_num_actions]; for (int i = 1; i < my_num_actions; i++) { my_cumul_dist[i] = my_dist[this_thread_global_array_idx * my_num_actions + i] + my_cumul_dist[i - 1]; } // Given sampled action which is a float in [0, 1], find the corresponding // discrete action. float sampled_float = curand_uniform(&rng_state); const int index = SearchIndex(my_cumul_dist, sampled_float, 0, (int)(my_num_actions - 1)); my_indices[this_thread_global_array_idx] = index; GetAction(my_actions, index_to_action, index, this_thread_global_array_idx, my_action_size); } __device__ float GetFirmProduction(float technology, float capital, float hours, float alpha) { if (hours < kLaborFloor) { hours = 0.0; } return technology * powf(capital, 1.0 - alpha) * powf(hours, alpha); } // -------------------- // Simulation Logic // -------------------- __global__ void CudaStep(float *cs_state_arr, float *cs_actions_arr, float *cs_rewards_arr, float *cs_state_arr_batch, float *cs_rewards_arr_batch, float *fm_state_arr, int *fm_action_indices_arr, float *fm_actions_arr, float *fm_rewards_arr, float *fm_state_arr_batch, int *fm_actions_arr_batch, float *fm_rewards_arr_batch, float *govt_state_arr, int *govt_action_indices_arr, float *govt_actions_arr, float *govt_rewards_arr, float *govt_state_arr_batch, int *govt_actions_arr_batch, float *govt_rewards_arr_batch, float *consumer_aux_batch, float *firm_aux_batch, int iter) { // This function should be called with 1 block per copy of the environment. // Within a block, each agent should have a thread. const int kWithinBlockAgentId = threadIdx.x; // return if we're on an extra thread not corresponding to an agent if (kWithinBlockAgentId >= kNumAgents) { return; } // ------------------------------------- // Start of variables and pointers defs. // ------------------------------------- // __shared__ variables are block-local: can be seen by each thread ** in the // block ** __shared__ float gross_demand_arr[kNumFirms]; __shared__ int num_consumer_demand_arr[kNumFirms]; __shared__ float hours_worked_arr[kNumFirms]; __shared__ float total_actually_consumer_arr[kNumFirms]; __shared__ float bought_by_importer_arr[kNumFirms]; __shared__ float next_global_state_arr[kGlobalStateSize]; __shared__ float tax_revenue_arr[kNumGovts]; __shared__ float total_utility_arr[kNumGovts]; __shared__ bool need_to_ration_this_good_arr[kNumFirms]; // whether or not to // ration good i float net_demand_arr[kNumFirms]; // amount demanded after budget constraints // by a consumer (ignore for non-consumers) int num_iter = (int)kMaxTime; AgentType ThisThreadAgentType = GetAgentType(kWithinBlockAgentId); float this_agent_reward = 0.0; // pointer to start of state vector // state vector consists of global state, then // agent-specific state global part is of same size for // all agents, but needs to be sliced out of different // arrays depending on agent type float *my_global_state_ptr; float *my_action_arr; // pointer to start of state vector in batch history float *batch_state_ptr; // sizes and indices for strided array access dim3 my_state_shape, my_state_idx, action_shape; // shape for batched array of scalars (action ind and reward) dim3 batch_single_shape, single_idx; // pointer to action index int *batch_action_value_ptr; // pointer to batch reward float *batch_reward_value_ptr; // pointers to action index for current arrays int *my_action_value_ptr; // pointers to reward index for current arrays float *my_reward_value_ptr; float *my_aux_batch_ptr; // ----------------------------------- // End of variables and pointers defs. // ----------------------------------- if (ThisThreadAgentType == kConsumerType) { // get current state my_state_shape = {kBatchSize, kNumConsumers, kStateSizeConsumer}; my_state_idx = {blockIdx.x, threadIdx.x, 0}; my_global_state_ptr = GetPointerFromMultiIndexFor3DFloatTensor( cs_state_arr, my_state_shape, my_state_idx); // index) // get current action action_shape = {kBatchSize, kNumConsumers, kActionSizeConsumer}; my_action_arr = GetPointerFromMultiIndexFor3DFloatTensor( cs_actions_arr, action_shape, my_state_idx); // index into the episode history and save prev state into it size_t my_batch_state_shape[] = {kBatchSize, num_iter, kNumConsumers, kStateSizeConsumer}; size_t my_batch_state_idx[] = {blockIdx.x, iter, threadIdx.x, 0}; batch_state_ptr = GetPointerFromMultiIndexFor4DTensor( cs_state_arr_batch, my_batch_state_shape, my_batch_state_idx); CopyFloatArraySlice(my_global_state_ptr, kStateSizeConsumer, batch_state_ptr); size_t my_aux_batch_shape[] = {kBatchSize, num_iter, kNumConsumers, kNumFirms}; my_aux_batch_ptr = GetPointerFromMultiIndexFor4DTensor( consumer_aux_batch, my_aux_batch_shape, my_batch_state_idx ); // Extract pointers to rewards, batch and current batch_single_shape = {kBatchSize, (unsigned int)num_iter, kNumConsumers}; single_idx = {blockIdx.x, (unsigned int)iter, threadIdx.x}; batch_reward_value_ptr = GetPointerFromMultiIndexFor3DFloatTensor( cs_rewards_arr_batch, batch_single_shape, single_idx); my_reward_value_ptr = &(cs_rewards_arr[blockIdx.x * kNumConsumers + threadIdx.x]); } if (ThisThreadAgentType == kFirmType) { // get current state size_t this_thread_firm_id = (threadIdx.x - kNumConsumers); my_state_shape = {kBatchSize, kNumFirms, kStateSizeFirm}; my_state_idx = {blockIdx.x, (unsigned int)this_thread_firm_id, 0}; my_global_state_ptr = GetPointerFromMultiIndexFor3DFloatTensor( fm_state_arr, my_state_shape, my_state_idx); // get current action action_shape = {kBatchSize, kNumFirms, kActionSizeFirm}; my_action_arr = GetPointerFromMultiIndexFor3DFloatTensor( fm_actions_arr, action_shape, my_state_idx); // index into the episode history and save prev state into it size_t my_batch_state_shape[] = {kBatchSize, num_iter, kNumFirms, kStateSizeFirm}; size_t my_batch_state_idx[] = {blockIdx.x, iter, this_thread_firm_id, 0}; batch_state_ptr = GetPointerFromMultiIndexFor4DTensor( fm_state_arr_batch, my_batch_state_shape, my_batch_state_idx); CopyFloatArraySlice(my_global_state_ptr, kStateSizeFirm, batch_state_ptr); dim3 my_aux_batch_shape = {kBatchSize, num_iter, kNumFirms}; dim3 aux_batch_idx = {blockIdx.x, iter, this_thread_firm_id}; my_aux_batch_ptr = GetPointerFromMultiIndexFor3DFloatTensor( firm_aux_batch, my_aux_batch_shape, aux_batch_idx ); // extract pointers to action indices and rewards, batch and current batch_single_shape = {kBatchSize, (unsigned int)num_iter, kNumFirms}; single_idx = {blockIdx.x, (unsigned int)iter, (unsigned int)this_thread_firm_id}; batch_action_value_ptr = GetPointerFromMultiIndexFor3DIntTensor( fm_actions_arr_batch, batch_single_shape, single_idx); batch_reward_value_ptr = GetPointerFromMultiIndexFor3DFloatTensor( fm_rewards_arr_batch, batch_single_shape, single_idx); const int kThisThreadFirmIdx = blockIdx.x * kNumFirms + this_thread_firm_id; my_action_value_ptr = &(fm_action_indices_arr[kThisThreadFirmIdx]); my_reward_value_ptr = &(fm_rewards_arr[kThisThreadFirmIdx]); } if (ThisThreadAgentType == kGovernmentType) { int this_thread_govt_id = (threadIdx.x - kNumConsumers - kNumFirms); my_state_shape = {kBatchSize, kNumGovts, kGovtStateSize}; my_state_idx = {blockIdx.x, (unsigned int)this_thread_govt_id, 0}; my_global_state_ptr = GetPointerFromMultiIndexFor3DFloatTensor( govt_state_arr, my_state_shape, my_state_idx); // index) // get current action action_shape = {kBatchSize, kNumGovts, kGovtActionSize}; my_action_arr = GetPointerFromMultiIndexFor3DFloatTensor( govt_actions_arr, action_shape, my_state_idx); // index into the episode history and save prev state into it size_t my_batch_state_shape[] = {kBatchSize, num_iter, kNumGovts, kGovtStateSize}; size_t my_batch_state_idx[] = {blockIdx.x, iter, this_thread_govt_id, 0}; batch_state_ptr = GetPointerFromMultiIndexFor4DTensor( govt_state_arr_batch, my_batch_state_shape, my_batch_state_idx); CopyFloatArraySlice(my_global_state_ptr, kGovtStateSize, batch_state_ptr); // extract pointers to action indices and rewards, batch and current batch_single_shape = {kBatchSize, (unsigned int)num_iter, kNumGovts}; single_idx = {blockIdx.x, (unsigned int)iter, (unsigned int)this_thread_govt_id}; batch_action_value_ptr = GetPointerFromMultiIndexFor3DIntTensor( govt_actions_arr_batch, batch_single_shape, single_idx); batch_reward_value_ptr = GetPointerFromMultiIndexFor3DFloatTensor( govt_rewards_arr_batch, batch_single_shape, single_idx); const int kThisThreadGovtIdx = blockIdx.x * kNumGovts + this_thread_govt_id; my_action_value_ptr = &(govt_action_indices_arr[kThisThreadGovtIdx]); my_reward_value_ptr = &(govt_rewards_arr[kThisThreadGovtIdx]); } // ---------------------------------------------- // State pointers and variables // Create pointers to agent-specific state that will be updated by the // simulation logic. // ---------------------------------------------- float *my_state_arr = &(my_global_state_ptr[kGlobalStateSize]); float *prices_arr = &(my_global_state_ptr[kIdxPricesOffset]); float *wages_arr = &(my_global_state_ptr[kIdxWagesOffset]); float *available_stock_arr = &(my_global_state_ptr[kIdxStockOffset]); float time = my_global_state_ptr[kIdxTimeOffset]; float income_tax_rate = my_global_state_ptr[kIdxIncomeTaxOffset]; float corporate_tax_rate = my_global_state_ptr[kIdxCorporateTaxOffset]; // ------------------------------- // Safely initialize shared memory // ------------------------------- if (ThisThreadAgentType == kFirmType) { int this_thread_firm_id = threadIdx.x - kNumConsumers; gross_demand_arr[this_thread_firm_id] = 0.0; num_consumer_demand_arr[this_thread_firm_id] = 0; hours_worked_arr[this_thread_firm_id] = 0.0; total_actually_consumer_arr[this_thread_firm_id] = 0.0; need_to_ration_this_good_arr[this_thread_firm_id] = false; } if (ThisThreadAgentType == kGovernmentType) { tax_revenue_arr[0] = 0.0; } __syncthreads(); // ------------------------------------- // End - Safely initialize shared memory // ------------------------------------- // ------------------------------------- // Process actions // ------------------------------------- if (ThisThreadAgentType == kConsumerType) { // amount demanded is just the first part of the action vector float *this_agent_gross_demand_arr = &(my_action_arr[kIdxConsumerDemandedOffset]); const float this_agent_hours_worked = my_action_arr[kIdxConsumerWorkedOffset]; const int worked_for_this_firm_id = (int)my_action_arr[kIdxConsumerWhichFirmOffset]; // here, need to scale demands to meet the budget. put them in a local array // *budgetDemanded logic should be: compute total expenditure given prices. // if less than budget, copy existing demands float __cost_of_demand = 0.0; for (int i = 0; i < kNumFirms; i++) { __cost_of_demand += this_agent_gross_demand_arr[i] * prices_arr[i]; } // Scale demand to ensure that total demand at most equals total supply // we want: my_state_arr being 0 always sends __scale_factor to 0 float __scale_factor = 1.0; if ((__cost_of_demand > 0.0) && (__cost_of_demand > my_state_arr[0])) { __scale_factor = my_state_arr[0] / __cost_of_demand; } // otherwise scale all demands down to meet budget // copy them into a demanded array for (int i = 0; i < kNumFirms; i++) { net_demand_arr[i] = __scale_factor * this_agent_gross_demand_arr[i]; } // adding up demand across threads **in the block** // somehow store amount demanded per firm in an array demanded (copy from // action) also store amount worked per firm in array worked // Every thread executes atomicAdd_block in a memory-safe way. for (int i = 0; i < kNumFirms; i++) { // sum across threads in block atomicAdd_block(&(gross_demand_arr[i]), net_demand_arr[i]); // increment count of consumers who want good i if (net_demand_arr[i] > 0) { atomicAdd_block(&(num_consumer_demand_arr[i]), 1); } } // increment total hours worked for firm i atomicAdd_block(&(hours_worked_arr[worked_for_this_firm_id]), this_agent_hours_worked); } // wait for everyone to finish tallying up their adding __syncthreads(); if (ThisThreadAgentType == kFirmType) { // check each firm if rationing needed int this_thread_firm_id = threadIdx.x - kNumConsumers; need_to_ration_this_good_arr[this_thread_firm_id] = ((gross_demand_arr[this_thread_firm_id] > 0.0) && (gross_demand_arr[this_thread_firm_id] > available_stock_arr[this_thread_firm_id])); } // wait for single thread to finish checking demands __syncthreads(); // ---------------------------------------- // Consumers: Rationing demand + Utility // ---------------------------------------- // Logic: // case 1: no overdemand // case 2: overdemand, but some want less than 1/N -- fill everyone up to // max(theirs, 1/N) case 3: overdemand, everyone wants more -- fill everyone // up to max(theirs, 1/N) float net_consumed_arr[kNumFirms]; // per consumer thread // always add negligible positive money to avoid budgets becoming small // negative numbers otherwise, when computing proportions, one may end up with // negative stocks. float cs_budget_delta = 0.01; float fm_budget_delta = 0.01; float capital_delta = 0.0; if (ThisThreadAgentType == kConsumerType) { // find out how much consumed for (int i = 0; i < kNumFirms; i++) { float __ration_factor = 1.0; if (need_to_ration_this_good_arr[i]) { // overdemanded __ration_factor = available_stock_arr[i] / gross_demand_arr[i]; } net_consumed_arr[i] = __ration_factor * net_demand_arr[i]; atomicAdd_block(&(total_actually_consumer_arr[i]), net_consumed_arr[i]); } // store amount actually consumed for this consumer CopyFloatArraySlice(net_consumed_arr, kNumFirms, my_aux_batch_ptr); // ---------------------------------------- // Compute consumer utility // ---------------------------------------- float hours_worked = my_action_arr[kIdxConsumerWorkedOffset]; int worked_for_this_firm_id = (int)my_action_arr[kIdxConsumerWhichFirmOffset]; // budget is first elem of consumer state, theta second float __theta = my_state_arr[1]; float __this_consumer_util = 0.0; float __total_hours_worked = 0.0; float __gross_income = 0.0; // Compute expenses // Each consumer can consume from each firm, so loop over them. for (int i = 0; i < kNumFirms; i++) { __this_consumer_util += GetCRRAUtil(net_consumed_arr[i], kCrraParam); cs_budget_delta -= prices_arr[i] * net_consumed_arr[i]; } // Compute income __total_hours_worked += hours_worked; __gross_income += wages_arr[worked_for_this_firm_id] * hours_worked; float __income_tax_paid = income_tax_rate * __gross_income; cs_budget_delta += (__gross_income - __income_tax_paid); // Update tax revenue (government) atomicAdd_block(&(tax_revenue_arr[kIdxThisThreadGovtId]), __income_tax_paid); // Compute reward this_agent_reward += __this_consumer_util - (__theta / 2.0) * (__total_hours_worked); } __syncthreads(); // ---------------------------------------- // Firms Exports: Add external consumption. // ---------------------------------------- if (ThisThreadAgentType == kFirmType ) { const int this_thread_firm_id = threadIdx.x - kNumConsumers; if (kUseImporter) { // sell remaining goods, if any, to importer, if price is high enough. float __this_firm_price = prices_arr[this_thread_firm_id]; float __stock_after_consumers = available_stock_arr[this_thread_firm_id] - total_actually_consumer_arr[this_thread_firm_id]; if (__this_firm_price >= kImporterPrice) { bought_by_importer_arr[this_thread_firm_id] = fmaxf(fminf(__stock_after_consumers, kImporterQuantity), 0.0); // floor to zero to avoid small negative floats } else { bought_by_importer_arr[this_thread_firm_id] = 0.0; } } else { bought_by_importer_arr[this_thread_firm_id] = 0.0; } } // ---------------------------------------- // Firms: Rationing demand + Utility // ---------------------------------------- if (ThisThreadAgentType == kFirmType) { const int this_thread_firm_id = threadIdx.x - kNumConsumers; float __this_firm_revenue = (total_actually_consumer_arr[this_thread_firm_id] + bought_by_importer_arr[this_thread_firm_id]) * prices_arr[this_thread_firm_id]; float __wages_paid = hours_worked_arr[this_thread_firm_id] * wages_arr[this_thread_firm_id]; // Firms can invest in new capital. This increases their production factor // (see GetFirmProduction). // here, after consumers consume, if price is >= than importer price, importer consumes up to their maximum of the goods, at the importer price float __gross_income = __this_firm_revenue - __wages_paid; capital_delta = fmaxf(my_action_arr[2] * __gross_income, 0.0); float __gross_profit = __gross_income - capital_delta; float __corp_tax_paid = corporate_tax_rate * fmaxf(__gross_profit, 0.0); fm_budget_delta = (__gross_profit - __corp_tax_paid); if (kIncentivizeFirmActivity) { if ((fm_budget_delta + my_state_arr[0]) > 0.0) { // if positive budget this_agent_reward += (kFirmBoostRewardFactor * __this_firm_revenue); } } this_agent_reward += (__gross_profit - __corp_tax_paid); atomicAdd_block(&(tax_revenue_arr[0]), __corp_tax_paid); float __production = GetFirmProduction(0.01, my_state_arr[kIdxFirmCapitalOffset], hours_worked_arr[this_thread_firm_id], my_state_arr[kIdxFirmAlphaOffset]); // ------------------- // Update global state // ------------------- // update prices in global state next_global_state_arr[kIdxPricesOffset + this_thread_firm_id] = my_action_arr[0]; // update wages in global state next_global_state_arr[kIdxWagesOffset + this_thread_firm_id] = my_action_arr[1]; // update stocks in global state next_global_state_arr[kIdxStockOffset + this_thread_firm_id] = available_stock_arr[this_thread_firm_id] - total_actually_consumer_arr[this_thread_firm_id] - bought_by_importer_arr[this_thread_firm_id] + __production; *my_aux_batch_ptr = bought_by_importer_arr[this_thread_firm_id]; // update overdemanded in global state next_global_state_arr[kIdxOverdemandOffset + this_thread_firm_id] = need_to_ration_this_good_arr[this_thread_firm_id] ? 1.0 : 0.0; } // ----------------- // Move time forward // ----------------- // Let first firm tick time if (ThisThreadAgentType == kFirmType) { const int this_thread_firm_id = threadIdx.x - kNumConsumers; if (this_thread_firm_id == 0) { next_global_state_arr[kIdxTimeOffset] = my_global_state_ptr[kIdxTimeOffset] + 1.0; } } __syncthreads(); // ---------------------------------------- // Subsidies // ---------------------------------------- // need to redistribute tax revenues if (ThisThreadAgentType == kConsumerType) { float __redistribution = tax_revenue_arr[0] / kNumConsumers; cs_budget_delta += __redistribution; } // ---------------------------------------- // Social welfare // ---------------------------------------- // After this point consumers and firms know their final reward, so can inform // the government thread via shared memory __syncthreads(); // ---------------------------------------- // Government sets taxes for the next round // ---------------------------------------- if (ThisThreadAgentType == kGovernmentType) { next_global_state_arr[kIdxIncomeTaxOffset] = my_action_arr[0]; next_global_state_arr[kIdxCorporateTaxOffset] = my_action_arr[1]; } __syncthreads(); // ----------------------------------------------- // Copy next_global_state_arr into my global state // ----------------------------------------------- // All agents need to see the updated global state CopyFloatArraySlice(next_global_state_arr, kGlobalStateSize, my_global_state_ptr); // ----------------------------------------------- // Update budgets // ----------------------------------------------- // Update budget (same for all agents) if (ThisThreadAgentType == kConsumerType) { my_state_arr[0] += cs_budget_delta; } if (ThisThreadAgentType == kFirmType) { my_state_arr[0] += fm_budget_delta; } // Add interest rate on savings if ((ThisThreadAgentType == kConsumerType) || (ThisThreadAgentType == kFirmType)) { if (my_state_arr[0] > 0.0) { my_state_arr[0] += my_state_arr[0] * kInterestRate; } } // Add new capital if (ThisThreadAgentType == kFirmType) { my_state_arr[kIdxFirmCapitalOffset] += capital_delta; } // Add new capital if ((ThisThreadAgentType == kFirmType) || (ThisThreadAgentType == kGovernmentType)) { *batch_action_value_ptr = *my_action_value_ptr; } // Update rewards in global state *my_reward_value_ptr = this_agent_reward; *batch_reward_value_ptr = this_agent_reward; } // ************************ // End of extern "C" block. // ************************ }