2021-08-31

from RL_brain_dueling import DuelingDQN      #导入DuelingDQN模块
from env import Scenario                      #导入Scenaio模块
import matplotlib.pyplot as plt            #作为python的绘图库,用plt作为简写
import scipy.io as scio
import tensorflow as tf                   #深度学习平台
import numpy as np                          #它是一个由多维数组对象和用于处理数组的例程集合组成的库
import seaborn as sns                      #作为pyhton的一个数据可视化的库,用sns简写



# def plot_reward(reward_ws, fig):
#
#     plt.figure(fig)
#     plt.plot(np.arange(len(reward_ws)),reward_ws)
#     plt.ylabel('hit_num')
#     plt.xlabel('content_num')

def plot_rewards(rewards, fig, tag="train", save=True):     #定义函数,存储到的总奖励?图像
    sns.set()                                                #制图的代码,用户快速设置seaborn的默认风格
    plt.figure(fig)                                           #创建一个画布?对象是fig,fig是?图像编号?
    plt.title("average cumulative rewards")                #标题,平均积累奖励
    plt.xlabel('epsiodes')                                #
    plt.plot(rewards, label='rewards')                    #系统在后台绘制关于奖励变动的图像,标题为“奖励”,是纵坐标为rewards值变动,横坐标就是0.1.2.3....这图会在上面的figure显示出来
    plt.legend()                                          #图列参数设置?默认?
    if save:
        plt.savefig("reward/rewards_curve_{}".format(tag))       #将图片存储
    plt.show()                                           #把之前在后台挥着的图像表露出来


def plot_hit_num(num, fig, name="none"):                         #命中率的函数、图表
    sns.set()                                                    #seaborn默认风格
    plt.figure(fig)
    plt.title("hit numbers")
    plt.xlabel('time slots')
    plt.plot(num, label=name)
    plt.legend()                           #设置图例?


def plot_loss(loss, fig):                                      #loss,Q预测与Q网络的差值,就是目标
    plt.figure(fig)
    plt.plot(loss)
    plt.ylabel('Loss')
    plt.xlabel('training steps')
    plt.show()


RL_flag = 1                           #作为一个旗标量
train_flag=1

if __name__ == '__main__':          #表示正式的代码入口
    sc = Scenario(ap_num=5, ue_num=10, time_slots=1000, buffer_size=25, content_size=1)    #将一个向量用一个简单的值表达出来
    #当决定要使用Dueling DQN,就会输入各种参数,并且初始化总行动步数,奖励空间,命中空间,以及反馈的偏差空间。
    #没有决定要训练时,判断是否准备好了存储的空间,并告知。
    #当决定开始训练的时候,设定总回合数。设定执行回合数,每个回合中重置用户关联的F—APs以及F-AP中缓存的文件。设定这个回合的总奖励为0,以及初始化观测状态矩阵,初始化step、用户变换位置次数、cost。
    #在所执行的每个回合里,要执行1000个时隙,其中,每隔一定的时隙会让各个用户变换一比位置,并查看变换位置后的命中情况,把这个情况放入命中矩阵当中。
     #在每个时隙中,都会对每个用户(10个)所请求的文件是否在关键的F-APs中缓存进行判断,如果都在,则总步数加一;如果都不在,则判断是否可以进行缓存,可以则总步数加一;如果已经存满,执行DQN,选择动作,并由动作得到各种观测值,将这些存储。并判断是否总步数>20000、启用了训练并且总步数可以整除4(既当总步数上20000后,每隔4步训练一次),则开始训练,训练结果进行存储,本回合的奖励进行相加。
     #只要对用户进行了一次操作,就会让总步数加一。也就是说当执行了200时隙时,就开始训练了。
     #当完成了一次回合,就把这个回合的奖励进行存储,用于之后训练。
    if RL_flag == 1:
        RL = DuelingDQN(n_actions=sc.buffer_size*sc.relevant_num, dueling=True,
                        n_features=2 * (sc.relevant_num * sc.buffer_size),
                        learning_rate=0.001, e_greedy=0.9, reward_decay=0.9,
                        replace_target_iter=5000, memory_size=20000, batch_size=32,
                        e_greedy_increment=0.00001,
                        n_point= 2*(sc.relevant_num * sc.buffer_size), output_graph=False)   #对DuelingDQN进行一个设置

        total_steps = 1
        rewards = []           #奖励
        hit_num = []           #命中
        loss = []              #Q估计与Q表格的差值
        saver = tf.train.Saver(max_to_keep=1000)       #保存和恢复变量,max_to_keep表明保存的最大checkpoint 文件数。用于神经网络训练

        if train_flag == 0:                 #表示刚刚开始
            np.random.seed(2)            #随机产生2个小于1数字,其中seed是随机生成器的种子
            episode = 1                   #第一回合
            checkpoint = tf.train.get_checkpoint_state('model/')       #通过tf.train.get_checkpoint_state函数,通过checkpoint找到"model"这个文件名
            if checkpoint and checkpoint.model_checkpoint_path:        #model_checkpoint_path保存了最新的tensorflow模型文件的文件名
                saver.restore(RL.sess, checkpoint.model_checkpoint_path)     #saver.restore重新加载数据模型。tf.train.Saver与saver.restore进行参数的保存与重现
                RL.epsilon = 1                                             #贪婪算法,为1的话,前后的选择没有衰减,从长远的分析
                print('model loaded! start running!')
            else:
                exit('model loaded failed!')
        else:
            episode = 100                                                   #设定回合数为100个回合
            np.random.seed(1)                                                 #随机产生1个数字
        # sc.interval_length = sc.ue_num*sc.ue_content_num
        # for ep in range(episode):
        for ep in range(5):                           #也是循环结构的一种,经常用于遍历字符串、列表,元组,字典,含义,ep依次表示range(5),即5个随机数,遍历完成后结束循环
            print('ep=',ep)                        #将每次遍历的ep值输出
            sc.episode = ep                        #设定系统的回合数为ep
            observation = sc.reset()              #调用env函数的Scenario中的reset函数,表示重置整个系统,每个F-AP以及每个F-AP内部缓存的文件,还有请求文件的概率都进行了重置
            # ob[0] = observation
            # observation = sc.fifo()
            ep_reward = 0                          #奖励为0
            ep_observation = []                     #这个回合的观测设置
            # sum_reward = 0
            ch=0                               #用户变换位置的次数
            # xlist = [x for x in range(sc.ap_num)]
            steps = 0                         #时隙为0
            cost = 0                            #误差
            for t1 in range(sc.time_slots-1):    #遍历所设定的每一个时隙
            # for t1 in range(50):
            # for t1 in range(5):
                if t1 % sc.duration == 0:      #每隔一段时间,进行一次操作
                    # if ep==0:
                    #     sc.change_relevant(ch)
                    # else:
                    sc.assign_relevant(ch)     #实现对用户关联的F-APs进行更新
                    ch+=1                       #变换次数加一
                hit_num_tmp = sc.hit_num(slot=t1)     #设定hit_num_tmp为在t1时刻的命中情况
                hit_num.append(hit_num_tmp)           #给命中的矩阵时刻更新从0时刻到1000时刻,命中的情况
                for t2 in range(sc.ue_num):                #遍历每一个用户
                    if not sc.all_request[t2 * sc.time_slots + t1] in sc.ap[sc.ue[t2].relevant[0]].cache_buffer.cache:   #在总的请求文件中,该用户在t1时刻请求的文件不在他关联的F-AP0这里缓存
                        if not sc.all_request[t2 * sc.time_slots + t1] in sc.ap[sc.ue[t2].relevant[1]].cache_buffer.cache:     #也不在他关联的F-AP1这里缓存时
                            if not sc.ap[sc.ue[t2].relevant[0]].cache_buffer.is_full():                                         #判断F-AP0是否缓存文件已满
                                sc.ap[sc.ue[t2].relevant[0]].cache_buffer.add_cache(sc.all_request[t2 * sc.time_slots + t1])     #没有满,则缓存用户请求的该文件
                            elif not sc.ap[sc.ue[t2].relevant[1]].cache_buffer.is_full():                                       #判断F-AP1是否缓存文件已满
                                sc.ap[sc.ue[t2].relevant[1]].cache_buffer.add_cache(sc.all_request[t2 * sc.time_slots + t1])    #没有满,则缓存用户请求的该文件
                            else:                                                                                                  #若即没有存储,又缓存文件已满的情况出现了
                                action = RL.choose_action(observation)                                                             #执行RL中选择动作的函数,大概率选择价值最好的,小概率随机选择
                                observation_, reward = sc.execute(action=action, ue_id=t2, slot=t1)                               #将选择的动作放入环境的execute中,获得下一个状态以及这次的收获
                                RL.store_transition(s=observation, a=action, r=reward/sc.ue_num, s_=observation_)                 #将这次的执行动作后的结果,放入存储库中,进行存储,方便之后随机抽出用于训练
                                if total_steps > 20000 and train_flag == 1 and total_steps % 4 == 0:                            #开始训练
                                    # print('learn\n')
                                    RL.learn()                                                                                   #运行学习
                                    loss.append(RL.cost)                                                                            #把误差记录在loss矩阵中
                                    cost = RL.cost                                                                                   #把误差记录下来,等会打出来
                                    # print('learn finish\n')
                                observation = observation_
                                # print(ob[t2])
                                # total_steps += 1
                                # sum_reward += reward
                                ep_reward += reward
                                steps += 1
                                print('--------------------')
                                print('Episode:{}/{}, steps:{}/10000, Reward:{}, Loss:{}'.format(ep + 1, episode, steps, ep_reward, cost))
                                # ep_reward.append(sum_reward)
                    total_steps += 1                                                                                    #只要轮到一个用户进行文件请求检查时,总步数就加一
                # ep_r.append(reward)
            rewards.append(ep_reward)                                                                                   #将该回合的总收获进行存储
            # reward_ws.append(ep_r)
                # ep_observation.append(observation)

        for k in range(sc.ap_num):                                                                                      #对每个F-AP进行遍历
            print('ap', k, 'is', sc.ap[k].cache_buffer.cache)                                                           #将所存储的文件输出
        # print(sc.all_request)
        # for j in range(sc.bbu_num):
        #     print('bbu', j, 'is', sc.bbu[j].cache_buffer.cache)
        #     print(sc.all_request)
        for i in range(sc.ue_num):                                                                                      #遍历每一个用户
            sc.ue[i].log_request()                                                                                      #将用户请求的文件遍历出来
            print(sc.ue[i].relevant)                                                                                     #输出用户关联的F-APs
        # sc.arrangement()
        # sc.calculate_delay()
        sc.reboot()                                                                                                     #分别对比4种方式的命中程度
        number = sc.fifo()
        sc.reboot()
        number2 = sc.lru()
        sc.reboot()
        number3 = sc.lfu()
        print(np.mean(hit_num), np.mean(number), np.mean(number2), np.mean(number3))
        # for k in range(sc.ap_num):
        #      print('ap', k, 'is', sc.ap[k].cache_buffer.cache)
        plot_hit_num(hit_num, 1, "Dueling")
        plot_hit_num(number3, 1, "LFU")
        plt.savefig("figures/hit_num_figure")
        plt.show()
        # plt.show()

        if train_flag == 1:                                                                                            #将文件进行保存,并把loss以及奖励用图片表示出来
            saver.save(RL.sess, 'model/savefiles', global_step=total_steps)
            scio.savemat('reward/reward.mat', {'rewards': rewards})
            # RL.plot_cost()
            # print(RL.cost_his)
            # plot_reward(ep_r, 2)
            # plot_reward(number, 2)
            plot_loss(loss, 3)
            plot_rewards(rewards, 2)
            # plt.show()

    else:                                                                                                               #当不绝对用Dueling DQN时的操作,对比2种方式的命中以及把F-AP的缓存文件以及用户的缓存文件输出
        # np.random.seed(2)
        sc.reboot()
        sc.fifo()
        sc.reboot()
        sc.lfu()
        for k in range(sc.ap_num):
            print('ap', k, 'is', sc.ap[k].cache_buffer.cache)
        # for j in range(sc.bbu_num):
        #     print('bbu', j, 'is', sc.bbu[j].cache_buffer.cache)
        print(sc.all_request)
        for i in range(sc.ue_num):
            # sc.ue[i].log_request()
            print(sc.ue[i].relevant)

上面是主函数,下面则是Dueling DQN

"""
The Dueling DQN based on this paper: https://arxiv.org/abs/1511.06581

View more on my tutorial page: https://morvanzhou.github.io/tutorials/

Using:
Tensorflow: 1.0
gym: 0.8.0
"""
  #个人认为代码中关于Dueling DQN的操作时通过经常性的更新Q估计,间断性的更新Q目标
# Q目标的更新方式是直接把Q估计赋予Q目标,然后Q估计继续运作,Q目标保存不边,在运行的过程种,不断用LOSS以及梯度下降法,去对反馈回动作,去反作用于Q估计,从而让Q估计于Q目标收敛,同时,在Q估计运行一定次数后,Q目标又会再次进行更新。
   #需要注意的是,强化学习都是在运行一定的回合后,有了一定的经验基础,才有了一定的学习能力,才知道哪里是有价值的,是正确的路

import numpy as np
import tensorflow as tf

np.random.seed(3)      #产生3个随机序列,之后再产生3个随机序列时,则和这里一样
tf.set_random_seed(1)   #


class DuelingDQN:              #DQN的初始化
    def __init__(
            self,
            n_actions,         #动作
            n_features,        #状态?
            learning_rate=0.001,  #NN中learning_rate学习速率
            reward_decay=0.9,  #奖励衰减函数
            e_greedy=0.9,      #贪婪算法
            replace_target_iter=200,  #更新Q现实网络参数的步骤数
            memory_size=500,   #记忆库尺寸
            batch_size=32,     #每次从记忆库中取的样本数量
            e_greedy_increment=None,
            output_graph=True,             #可以看流程图
            dueling=True,                 #用于判别是否使用了dueling DQN
            n_point=20,
            sess=None,
    ):
        self.n_actions = n_actions           #动作
        self.n_features = n_features        #观测的相关特诊?
        self.lr = learning_rate              #学习速率
        self.gamma = reward_decay             #奖励衰减
        self.epsilon_max = e_greedy            #贪婪算法?
        self.replace_target_iter = replace_target_iter  #隔多少步后将target net 的参数更新为最新的参数,replace_target_iter为更新target network的步数,防止target network和eval network差别过大
        self.memory_size = memory_size                 #整个记忆库得容量
        self.batch_size = batch_size                #样本数量,在随机降低梯度中会用到
        self.n_points=n_point                      
        self.epsilon_increment = e_greedy_increment  #表示不断扩大epsilon,以便有更大的概率拿到好的值
        self.epsilon = 0 if e_greedy_increment is not None else self.epsilon_max ## 是否开启探索模式, 并逐步减少探索次数

        self.dueling = dueling      # decide to use dueling DQN or not  是否决定使用DQN

        self.learn_step_counter = 0      #用这个记录学习了多少步,可以让self.epsilon根据这个步数来不断提高
        self.memory = np.zeros((self.memory_size, n_features*2+2))#行(高度)为存储记忆的数量,列为(observation, action, reward, observation_)的长度,即存储的整体容量
        self._build_net()                                    #建立DQN
        t_params = tf.get_collection('target_net_params')   #该函数可以用来获取key集合中的所有元素,返回一个列表。返回具有给定名称的集合中的值列表(目标Q),提取 target_net 的参数
        e_params = tf.get_collection('eval_net_params')    #该函数可以用来获取key集合中的所有元素,返回一个列表。(估计Q),提取  eval_net 的参数
        # 将eval_network中每一个variable的值赋值给target network的对应变量
        self.replace_target_op = [tf.assign(t, e) for t, e in zip(t_params, e_params)]    # 这个函数的功能主要是把t的值变为e,t和e都是从zip中按顺序排出,应该是DQN中把更新的值去代替原来的,替换变量

        if sess is None:   #绘图?全局初始化变量?
            self.sess = tf.Session()            #操作指令
            self.sess.run(tf.global_variables_initializer())  #对全局进行一个初始化  #用于记录# 记录所有 cost 变化
        else:
            self.sess = sess

        if output_graph:         #输出图片?
            self.merged_loss = tf.summary.merge([self.aa])     #tf.summary.merge一般选择要保存的信息,其中还需要用到tf.get_collection()函数,保存aa,即保存损失函数
            self.merged_W = tf.summary.merge([self.log_Q1,self.log_Q2,self.log_Q3,self.log_Q4,self.log_Q5])  #同理用于保存信息,记录Q表格中的各种信息
            self.log=1
            self.writer = tf.summary.FileWriter("logs/", self.sess.graph)         #指定一个文件用来保存图,这里用于看流程图。。
            print('log created!')
        else:
            self.log=0
        self.cost_his = []

    def _build_net(self):    #构建DQN
        def build_layers(s, c_names, n_l1, w_initializer, b_initializer):    #initializer初始化,即输入各种初始化,s是名称,c_name是图片存储位置,用于在一定步数之后更新target network,w..是权值,b..是偏置向量
            with tf.variable_scope('l1'):                                     #用于定义创建变量,来操作文件上下层管理器?  在l1这个变量域中
                w1 = tf.get_variable('w1', [self.n_features, n_l1], initializer=w_initializer, collections=c_names) #定义一个tensorflow变量,矩阵行为feature,竖为l1,数值从w_initializer中出来,图形集中在collection中,权值矩阵
                b1 = tf.get_variable('b1', [1, n_l1], initializer=b_initializer, collections=c_names)   #初始化偏置向量,形状为[1,神经元个数],通过下面的函数看出,#n_l1为network隐藏层神经元的个数
                l1 = tf.nn.relu(tf.matmul(s, w1) + b1)   #线性整流函数(Rectified Linear Unit, ReLU),又称修正线性单元,用于卷积神经网络中的第二个步骤,tf.nn.relu()函数的目的是,将输入小于0的值幅值为0,输入大于0的值不变,tf.matmul中s与w相乘
          #  with tf.variable_scope('l2'):
           #     w2 = tf.get_variable('w2', [n_l1, n_l1], initializer=w_initializer, collections=c_names)
            #    b2 = tf.get_variable('b2', [1, n_l1], initializer=b_initializer, collections=c_names)
             #   l2 = tf.nn.relu(tf.matmul(l1, w2) + b2)
           # with tf.variable_scope('l3'):
            #    w3 = tf.get_variable('w3', [n_l1, n_l1], initializer=w_initializer, collections=c_names)
             #   b3 = tf.get_variable('b3', [1, n_l1], initializer=b_initializer, collections=c_names)
              #  l3 = tf.nn.relu(tf.matmul(l2, w3) + b3)

            if self.dueling:                              #决定使用duling DQN时候
                # Dueling DQN
                with tf.variable_scope('Value'):           #创建变量,下面都是在这个变量领域中,这是duling DQN中的V,即价值那一块,是数值部分
                    wV = tf.get_variable('wV', [n_l1, 1], initializer=w_initializer, collections=c_names)   #构建变量,这里是全职向量
                    bV = tf.get_variable('bV', [1, 1], initializer=b_initializer, collections=c_names)     #同理,构建变量,这里是偏置向量
                    self.V = tf.matmul(l1, wV) + bV                                                          #把价值用等式求出

                with tf.variable_scope('Advantage'):    #创建变量,下面都是在这个变量领域中,这是duling DQN中的另一部分,用于判断决策是否好
                    wA = tf.get_variable('wA', [n_l1, self.n_actions], initializer=w_initializer, collections=c_names)
                    bA = tf.get_variable('bA', [1, self.n_actions], initializer=b_initializer, collections=c_names)
                    self.A = tf.matmul(l1, wA) + bA

                with tf.variable_scope('Q'): #合并V和A,为了不让A直接学成Q,减去了一个A的均值(相当于一种约束,让A是所有值和为零)
                    out = self.V + (self.A - tf.reduce_mean(self.A, axis=1, keep_dims=True))     # Q = V(s) + A(s,a),为了防止V等于0时候,Q就等于A了,所以要减去一个值,tf.reduce_mean()函数是计算平均值的

            else:                                           #当不适用duling DQN时,则采用DQN算法
                with tf.variable_scope('Q'):                  #不使用duling DQN时候,就直接用DQN计算法则
                    w2 = tf.get_variable('w2', [n_l1, self.n_actions], initializer=w_initializer, collections=c_names)
                    b2 = tf.get_variable('b2', [1, self.n_actions], initializer=b_initializer, collections=c_names)
                    out = tf.matmul(l1, w2) + b2

            return out                                             #结束

        # ------------------ build evaluate_net ------------------
        self.s = tf.placeholder(tf.float32, [None, self.n_features], name='s')  # input在神经网络构建graph的时候在模型中的占位,相当于提前站空前,状态等各种参数的构建
        self.q_target = tf.placeholder(tf.float32, [None, self.n_actions], name='Q_target')  # for calculating loss   目标Q值的构建
        with tf.variable_scope('eval_net'):                                            #创建一个变量,下面所有函数在这个变量内,估计网络
            c_names, n_l1, w_initializer, b_initializer = \
                ['eval_net_params', tf.GraphKeys.GLOBAL_VARIABLES], self.n_points, \            #??????tf.GraphKeys收集所有的图片,并且在后面方便调用,而GLOBAL_VARIABLES则代表默认收集了所有的变量对象。
                tf.random_normal_initializer(0., 0.3), tf.constant_initializer(0.1)  # config of layers    tf.random_normal_initializer(0., 0.3)表示正态分布产生张量的初始化器,均值为0,方差为0.3,给权值赋予了正态分布的情况。tf.constant_initializer表示所有的偏差都是0.1

            self.q_eval = build_layers(self.s, c_names, n_l1, w_initializer, b_initializer) #构建估计Q的duling DQN了
            #self.max_q=np.max(self.q_eval)
            #self.min_q=np.min(self.q_eval)
            self.log_Q1 = tf.summary.scalar('Q_max', (tf.reduce_max(self.q_eval)))     #tf.summary.scalar用来显示标量信息,名称是Q_max,后面则是实数标量值,取最大
            self.log_Q2 = tf.summary.scalar('Q_min', (tf.reduce_min(self.q_eval)))     #名称是Q_min,后面则是实数标量值,取最小
            self.log_Q3 = tf.summary.scalar('Q_mean', (tf.reduce_mean(self.q_eval)))   #取平均
            self.log_Q4 = tf.summary.scalar('Q_d', (tf.reduce_max(self.q_eval)-tf.reduce_min(self.q_eval)))   #取最大与最小的相差
            self.log_Q5 = tf.summary.scalar('Q_def', (tf.reduce_max(self.q_eval) - tf.reduce_min(self.q_eval))/tf.reduce_mean(abs(self.q_eval))) #最大与最小的差去除以平均
            #self.log_Q2 = tf.summary.scalar('Q2', self.min_q)


        with tf.variable_scope('loss'):                                                #LOSS,来源于Q目标与Q估计的相减,最后再反馈回Q目标,去降低.损失函数的存在,是告诉我们,当训练出错时候,可以告知错误,错误之后就是损失函数,这个可以再重新反向传播,从而训练网络
            self.loss = tf.reduce_mean(tf.squared_difference(self.q_target, self.q_eval))   #让两个张量,即Q目标与Q估计做差的平方,得到的结果取平均,这个平均可以有效降低维度
            self.aa=tf.summary.scalar('loss', self.loss)                                     #让aa设置为LOSS的标量信息

        with tf.variable_scope('train'):                                                #定义一个“训练”的变量,这就是梯度下降
            self._train_op = tf.train.AdamOptimizer(self.lr).minimize(self.loss)          #lr是学习速率,tf.train.AdamOptimizer自适应矩估计,是一个寻找全局最优点的优化算法,引入了二次方梯度校正。这里是优化器,对应的是梯度下降函数那块
           # self._train_op = tf.train.RMSPropOptimizer(self.lr).minimize(self.loss)

        # ------------------ build target_net ------------------                          构建Q目标
        self.s_ = tf.placeholder(tf.float32, [None, self.n_features], name='s_')    # input  在神经网络构建graph的时候在模型中的占位,相当于提前站空前,状态等各种参数的构建,None, self.n_features表示列是n_features,行不定
        with tf.variable_scope('target_net'):                                          #定义一个变量,名称叫目标Q网络
            c_names = ['target_net_params', tf.GraphKeys.GLOBAL_VARIABLES]                #将这里的流程图等打包放入taget_net_params中
            self.q_next = build_layers(self.s_, c_names, n_l1, w_initializer, b_initializer) #构建Q目标的dueling DQN了

    def store_transition(self, s, a, r, s_):                                                 #存储记忆
        if not hasattr(self, 'memory_counter'):                                              #hasattr() 函数用于判断对象是否包含对应的属性。判断self是否有memory_counter,有则返回TRUE,没有则返回FALSE
            self.memory_counter = 0                                                            #如果没有,则设置一个,为0,存储记忆的回合数
        transition = np.hstack((s, [a, r], s_))                                               #将所有消息进行整合堆叠,是这个回合的状态,动作,奖励,以及下一个状态存储
        # 总 memory 大小是固定的, 如果超出总大小, 旧 memory 就被新 memory 替换
        index = self.memory_counter % self.memory_size                                           #%相除返回余数
        self.memory[index, :] = transition                                                     #index为索引,在index这一行中,把所有整合信息放入记忆库中,替换原来的存储,如果没有,就相当于赋值了
        self.memory_counter += 1                                                              #记忆次数加一

    def choose_action(self, observation):                                                      #选择动作
        # 根据observation(state)选行为
        # 使用eval network选出state下的行为估计
        # 将observation的shape变为(1, size_of_observation),行向量变为列向量才能与NN维度统一
        observation = observation[np.newaxis, :]                                               #np.newaxis表示增加维度,目的是为了之后可以进行权值分配
        if np.random.uniform() < self.epsilon:                                                 #np.random.uniform生成均匀分布的随机数,默认0-1,大概率选择actions_value最大下的动作,小概率选择随机动作
            if (self.log):                                                                      #已有流程图的情况下
                actions_value,summary = self.sess.run([self.q_eval,self.merged_W], feed_dict={self.s: observation})    #动作价值是由Q估计中的各种Q的最大最小,差值等决定出来的
                self.writer.add_summary(summary, global_step=self.learn_step_counter)                                          #add_summary()方法将训练过程数据保存在filewriter指定的文件中,global_step代表全局步数,也代表学习了多少步
            else:
                actions_value= self.sess.run(self.q_eval, feed_dict={self.s: observation})     #没有流程图的情况下,说明没有运行结束,所以单纯建立Q估计,填入的参数就是observation

            action = np.argmax(actions_value)                                                   #np.argmax返回一个numpy数组中最大值的索引值,这就是Q-learning的行动决策,选择动作价值进行行动。
        else:                                                                                   #如果选不出动作价值最大的
            action = np.random.randint(0, self.n_actions)                                       #np.random.randint函数的作用是,返回一个随机整型数,即随机选择动作
        return action




    def learn(self):                                                                           #学习部分
        if self.learn_step_counter % self.replace_target_iter == 0:                            #代表了每隔一段所设定的需要更新目标Q的时间
            self.sess.run(self.replace_target_op)                                              #当时间达到,或者说运行次数达到后,便进行一次更新目标Q,把Q估计赋予Q目标
            print('\ntarget_params_replaced\n')                                                #告知我们,已经对目标Q进行了更新

        sample_index = np.random.choice(self.memory_size, size=self.batch_size)                 #样本的索引,从500个中,随机的抽出batch,即32个样本出来,生成一个随机的样本(数)
        batch_memory = self.memory[sample_index, :]                                             #定义一个样本的容量,抽取记忆表self.memory中前sample_index行(容量)
       # print(tmp)
        #print(tmp[0])
        #batch_memory,range,min=self.autoNorm(tmp)
        #print(batch_memory)
        q_next = self.sess.run(self.q_next, feed_dict={self.s_: batch_memory[:, -self.n_features:]}) # next observation...q_next由目标值网络用记忆库中倒数n_features个列(observation_)的值做输入
        q_eval = self.sess.run(self.q_eval, {self.s: batch_memory[:, :self.n_features]})               #上下都是运行2个神经网络,参数都是根据样本里面的各种参数进行输入决定...q_eval由预测值网络用记忆库中正数n_features个列(observation)的值做输入

        q_target = q_eval.copy()                                                                       #让Q目标对Q估计进行一个复制,父目录复制,子目录会随原始改变而变化

        batch_index = np.arange(self.batch_size, dtype=np.int32)                                      #设定一个被抽取样本的索引,从0开始,每个样本下标
        eval_act_index = batch_memory[:, self.n_features].astype(int)                                 #返回一个长度为32的动作列表,从记忆库batch_memory中提取,记录每个样本执行的动作
        reward = batch_memory[:, self.n_features + 1]                                                 #返回一个长度为32奖励的列表,提取出记忆库中的reward,记录每个样本动作的奖励


        q_target[batch_index, eval_act_index] = reward + self.gamma * np.max(q_next, axis=1)          #对Q目标的表格进行更新,生成每个样本中q值对应动作的更新,即生成的q现实,
        # 假如在这个 batch 中, 我们有2个提取的记忆, 根据每个记忆可以生产3个 action 的值:
        # q_eval =[[1, 2, 3],[4, 5, 6]], 另q_target = q_eval.copy()
        # 然后根据 memory 当中的具体 action 位置来修改 q_target 对应 action 上的值:
        # 比如在:记忆 0 的 q_target 计算值是 -1, 而且我用了 action 0;忆1的 q_target 计算值是-2, 而且我用了 action 2:
        # q_target =[[-1, 2, 3],[4, 5, -2]]
        # 所以 (q_target - q_eval) 就变成了:[[(-1)-(1), 0, 0],[0, 0, (-2)-(6)]]
        # 最后我们将这个 (q_target - q_eval) 当成误差, 反向传递会神经网络
        # 所有为 0 的 action 值是当时没有选择的 action, 之前有选择的 action 才有不为0的值.
        # 我们只反向传递之前选择的 action 的值

        if(self.log):                                                                                #当有流程图时候
            _, summary,self.cost = self.sess.run([self._train_op,self.merged_loss ,self.loss],        #根据上面注释,将变换后的Q目标去更新LOSS以及用于训练当中,并进行存储到summary中,从而反馈给动作
                                             feed_dict={self.s: batch_memory[:, :self.n_features],
                                                        self.q_target: q_target})
            self.writer.add_summary(summary, global_step=self.learn_step_counter)                     #将信息整合进文件夹中,从而在流程图可以看到
        else:
            _, self.cost = self.sess.run([self._train_op, self.loss],
                                     feed_dict={self.s: batch_memory[:, :self.n_features],
                                                self.q_target: q_target})

        self.cost_his.append(self.cost)              #记录 cost 误差

        self.epsilon = self.epsilon + self.epsilon_increment if self.epsilon < self.epsilon_max else self.epsilon_max  #?#每调用一次learn,降低一次epsilon,即行为随机性
        self.learn_step_counter += 1                                                                    #学习步数加一

    def plot_cost(self):                                                                              #画图,画出误差的图像
        import matplotlib.pyplot as plt                                                                  # #作为python的绘图库,用plt作为简写
        if self.log:                                                                                  #有流程图时
            self.writer.close()
        #self.writer.add_summary(rs, self.learn_step_counter)
        plt.plot(np.arange(len(self.cost_his)), self.cost_his)
        plt.ylabel('Cost')
        plt.xlabel('training steps')
        plt.show()




下面是外部环境

import numpy as np   #可以很自然地使用数组和矩阵,NumPy包含很多实用的数学函数,涵盖线性代数运算、傅里叶变换和随机数生成等功能


class UE:           #定义一个类别,UE指用户
    def __init__(self, ue_id, time_slots=1000, content_size=1, relevant_num=2):   #哪个调用了这个类别,哪个就是self,或者说self就是调用得对象。__init__() 是类的初始化方法;它在类的实例化操作后 会自动调用,不需要手动调用;具体设定是调度时间为1000s,每个用户关联2个F-APS,索要内容为1个文件
        self.ue_id = ue_id                                              #用户ID
        self.time_slots = time_slots                                    #调度时隙
        self.content_size = content_size                                #用户内容??
        self.relevant = np.zeros(relevant_num, dtype=np.int_)            #关联的矩阵,用于判定在哪个F-APS中
        self.movement = np.zeros((1000, relevant_num), dtype=np.int_)   #用户移动,用户随机变动矩阵,数据类型为整数长形
        self.relevant_num = relevant_num                                #设定每个用户都固定关联2个F-APS
        self.request = np.zeros(self.time_slots, dtype=np.int_)         #用户需求

    def log_request(self):                                             #定义函数名称为log_request,用户请求的文件?
        print('request_array=', self.request)                           #将用户所要请求的文件一一列举出来


class AP:  #接入点,与用户相接入的第一个F-AP
    def __init__(self, ap_id, buffer_size=25, content_size=1):
        self.ap_id = ap_id                             #对于用户来说,第一个所与他连接的电脑
        self.buffer_size = buffer_size                  #系统可以缓存的文件
        self.cache_buffer = Buffer(self.buffer_size)   #系统缓存的文件
        self.cache_buffer.zeros_init_buffer()       #对系统容量进行一个预先分配?
        self.content_size = content_size          #文件的大小,横为1,即假设文件大小一致


class AP2:  #与用户相接入的第二个F-AP
    def __init__(self, ap_id, buffer_size=25, content_size=1):
        self.ap_id = ap_id
        self.buffer_size = buffer_size
        self.cache_buffer = Buffer(self.buffer_size)
        # self.cache_buffer.zeros_init_buffer()
        self.content_size = content_size


class Buffer:                         #定义一个新的类
    def __init__(self, buffer_size=25):
        self.cache = []            #缓存文件为空集
        self.current_size = 0       #此时缓存的文件数量
        self.buffer_size = buffer_size    #系统可以缓存的文件个数

    def is_empty(self):         #如果系统为空的
        return len(self.cache) == 0     #返回一个长度为0,即缓存文件的长度是0

    def is_full(self):             #如果系统判断为满的
        return len(self.cache) == self.buffer_size    #返回一个长度为系统可以缓存的最大文件数,先赋予系统缓存文件的长度是这个系统所能容纳的最大长度

    def clear(self):         #清除已经缓存的文件
        self.cache = []         #将缓存的文件变为空集
        self.current_size = 0.0        #此时系统中的文件个数为0

    def delete_cache(self):  #删除缓存的文件
        if not self.is_empty():    #判断,如果对象不是空的
            self.cache = self.cache[1:]   #把缓存的文件的第一个删除
            self.current_size -= 1     #缓存个数减少一个
        else:
            print('empty!!!')    #此时判断为空集,直接打出“empty”
            return -1              #返回-1

    def add_cache(self, content):     #如果要增加文件量
        if not self.is_full():     #首先先判断对象是否已经是满的,即是否缓存文件达到能缓存的最大值
            self.cache.append(content)   #在缓存的末尾增加一个元素,也就是文件
            self.current_size += 1    #缓存个数增加1个
        else:
            self.delete_cache()      #但判断其是满的时候,回到delete函数,进行删除
            self.add_cache(content)  #接下来在进行增加文件

    def resort(self, content):       #手段,对文件处理的手段
        self.cache.remove(content)   #对缓存的文件进行移除。但这和delete有什么区别?
        self.cache.append(content)  #对缓存的文件进行增加,增加在当前已经缓存的文件末尾

    def update(self, change_id=0, content=0):#用于更新基站缓存,替换或者增加,用请求更新(看不懂)
        if not change_id > self.current_size:#chang_id比当前的缓存长度短,那就替换掉对应id的内容
            self.cache[change_id] = content
        else:   #chang_id比当前存在的缓存长度长,若系统缓存未满就依次存放,满了就替换最久的
            self.add_cache(content)

    def zeros_init_buffer(self):       #归零?初始化
        self.cache = np.zeros(self.buffer_size, dtype=int)   #给系统预先分配系统所能缓存文件的容量
        self.current_size = self.buffer_size        #告知现在缓存的文件个数就是系统最大的容量
        # print('init buffer is', self.cache, 'and size is', self.buffer_size)


class Scenario:        #初始化定义
    def __init__(self, ap_num=5, ue_num=10, time_slots=1000, buffer_size=25, content_size=1):#__init__,两个下划线开头的init具备私有的含义,这是不能在类的外部被使用或者访问,第一个参数必须是self
        self.ap_num = ap_num       #设置了AP的数量
        self.ue_num = ue_num       #设置了用户的数量
        self.time_slots = time_slots   #总的时间,每1秒会有一次调度,总共调度1000次
        self.buffer_size = buffer_size   #每个AP所能缓存的最大文件设置
        self.content_size = content_size   #设置每个文件的大小,统一为1,即是大家文件都一样
        self.episode = 1                 #迭代次数,回合数
        self.relevant_num = 2               #每个用户关联的电脑数为2
        self.duration=10                  #作用与用户变化的时刻
        self.ap_hit_num = np.zeros(ue_num, dtype=np.float_)      #ap的命中文件数量,以用户的个数为最大命中次数
        self.zipf=1.1                         #公式
        self.ob_steps=125       #总状态?
        # self.all_request = np.random.zipf(1.3,size=self.time_slots*self.ue_num)
        # self.bbu = {}
        self.all_request = np.zeros(self.time_slots*self.ue_num, dtype=int)      #总的用户数去乘上时间就是所有要求的数量,这是假定了每个用户每次就需求一个文件
        self.request_frequency1 = np.zeros((self.relevant_num*self.buffer_size), dtype=np.int_)      #每个用户关联的数量*系统能存的最大容量,这应该是对于用户来说能请求的最大文件数,每个用户每次请求1个,那么请求的次数,最大就是其关联F-APs的容量
        # self.request_frequency2 = np.zeros((self.ue_num), dtype=np.int)
        self.ap = {}      #设定了用户的集合
        self.ue = {}          #{}与()的区别是什么?
        # self.init_bbu()
        self.init_ap()     #定义函数?
        self.init_ue()       #调用函数?
        self.request_sequence()               #请求文件的次序
        self.change_relevant()                  #用户变化位置后,与之相关联的F-APs
        self.request_sorted = np.zeros((2, self.relevant_num*self.buffer_size), dtype=np.int_)   #需求的分类,应该是对应用户连接的2个F-APs所设置的,但是为什么是相关数量*缓存容量,并且数据类型定义为整数
        self.observation = np.zeros(2 * (self.relevant_num*self.buffer_size), dtype=np.float_)       #????
        # self.observation = np.zeros(1 * (self.relevant_num * self.buffer_size), dtype=np.float)

    def init_ap(self):    #定义每一个F-AP
        for i in range(self.ap_num):
            self.ap[i] = AP(ap_id=i, buffer_size=self.buffer_size, content_size=self.content_size)

    def init_ap2(self):    #每一个F-AP都可以去变成用户所连接的另一个F-AP,但是这样设置是否会出现一个用户同时连2次相同的F-APs,这与上面就是少了预先分配,但是有什么不同?
        for i in range(self.ap_num):
            self.ap[i] = AP2(ap_id=i, buffer_size=self.buffer_size, content_size=self.content_size)

    def init_ue(self):    #定义用户的函数
        # n = random.randint(1, 10)
        # np.random.seed(3)
        # xlist = [x for x in range(self.ap_num)]
        xlist = list(range(self.ap_num)) #list是创建列表,range是创建一个整数列表,一般用在for中,从0开始一直到ap_num-1
        for i in range(self.ue_num):      #让i=每个用户
            self.ue[i] = UE(ue_id=i, time_slots=self.time_slots, content_size=self.content_size, relevant_num=self.relevant_num) #表达UE的设置概念,时隙、文件大小,每个用户关连的F-AP固定为2,对上面的函数进行调用,并设置好参数
            self.ue[i].relevant = np.random.choice(xlist, replace=False, size=self.relevant_num) #用户随机选择与之关联F-APs,用np.random.choice表示从数组中随机抽出样本,从xlist中随机抽出数字,并组成size所设置的2个,即抽2个随机数,replace中,ture表示可以取相同,false表示不可取相同数字。
            # print(xlist)            上面就是随机化与用户相连接的F-APs,是初始情况
            # self.ue[i].request = np.random.zipf(1.3, size=self.time_slots)
            # print(self.ue[i].relevant)

    def change_relevant(self):       #每过一段时隙后,用户变换位置所连接的F-APs可能发送变化所设置的函数
        # xlist = [x for x in range(self.ap_num)] #[0,1,2,3,4]
        xlist = list(range(self.ap_num)) #同上,建立一个列表,列表里面是从0到ap_num-1的数字排序
        for i in range(self.ue_num):  #定义i经历了每个用户序列,从而达到让每个用户都进行变化
            for ch in range(self.time_slots//self.duration):       #设置了变化的次数
                self.ue[i].movement[ch] = np.random.choice(xlist, replace=False, size=self.relevant_num) #让每个用户的每次变化存储到movement这个向量中,从而完成对用户变化位置的一个记录

    def assign_relevant(self,ch): #把上面经过移动后,与用户相关联的F-APs替换成目前用户相关联的F-APs
        # xlist = [x for x in range(self.ap_num)]
        for i in range(self.ue_num):    #对每一个用户进行一个遍历,从而完成对每一个用户信息的修改
            self.ue[i].relevant = self.ue[i].movement[ch]     #通过这里,完成对用户移动后关联的新F-APs的修改

    def request_sequence(self): #用户请求的次序?看不懂,请求序列。用于齐夫分布
        # self.all_request = np.random.zipf(1.3, self.time_slots*self.ue_num)
        for i in range(self.ue_num): #[0,10)   遍历每一个用户,对每一个用户的信息都进行修改
            self.ue[i].request = np.random.zipf(self.zipf, self.time_slots)*(i+1)  #用户请求满足齐夫分布,设定参数为1.1,返回数组形状为1000个点,即采样1000个点,后面为啥乘(i+1),是否是因为齐夫分布的公式让它乘的?????
            self.all_request[i*self.time_slots:(i+1)*self.time_slots] = np.copy(self.ue[i].request )  #copy是拷贝,深拷贝,这里的改变不会影响上面,以不同调度时隙进行划分看总请求,相当于说,每次调度时隙,都可以重新计算用户的请求[:]表示从前面都后面一次列出
        # n = random.randint(1, 10)
        # np.random.seed(1)
        # self.all_request=np.random.zipf(1.3, self.time_slots*self.ue_num)

    def reset(self):   #初始化所有
        for j in range(self.ap_num):  #让j去遍历每一个F-AP,从而完成对其的设置
            self.ap[j].cache_buffer.clear()   #让每个F-AP去运行上面的clear函数,从而完成对容量的清除,但是调用函数是这样用的吗?
        # self.init_bbu()
        self.init_ap()                  #重新定义每一个F-AP,去设置他的容量这种参数
        # self.init_ue()
        self.observe(step=0,ue_id=0)   #初始化观测,相当于第0个时隙观测,用户为0个
        return np.array(self.observation)    #返回一个数组

    def reboot(self):   #重新启动F-AP
        for j in range(self.ap_num):   #让j遍历每一个F-AP
            self.ap[j].cache_buffer.clear()   #让每个F-AP去运行上面的clear函数,从而完成对容量的清除
        self.init_ap2()             #执行函数,对这个进行参数设置,但是与上面不同在哪?为什么这样?对比算法,与其他算法对比。

    def hit_num(self,slot):   #命中
        self.ap_hit_num = np.zeros(self.ue_num, dtype=np.float_)    #对命中的容量,预先分配容量,可以命中的最大值就是总体用户,也就是说,每个用户都被命中
        for i in range(self.ue_num):                                #对每个用户进行遍历
            ap_relevant = self.ue[i].relevant                       #对F-AP来说,其关联的ue设置,直接设置一个参量,对关联考察,进入if查看情况
            if self.ue[i].request[slot] in self.ap[ap_relevant[0]].cache_buffer.cache:    #判断ue的请求文件是否在用户关联的其中一个F-AP缓存的其中。(看不懂这里的操作)
                        self.ap_hit_num[i] = 1                                 #命中数加1
            else:
                if self.ue[i].request[slot] in self.ap[ap_relevant[1]].cache_buffer.cache:  #判断ue的请求是否在用户关联的另一个F-AP缓存的其中
                        self.ap_hit_num[i] = 1                                   #命中+1
        tmp = 0                                                                 #设置参量,便于观察总的命中多少
        for l in range(self.ue_num):                                              #对每个用户进行遍历
            tmp += self.ap_hit_num[l]                                         #让每个用户的命中个数相加
        return tmp                                                            #返回一个总体命中数


    def action_transaction(self, action=0):       #行动决策,0是一个默认值,具体要看输入的参数
        action1 = self.request_sorted[0][action]     #去取要求的第一行的元素,即索引
        relevant_id = action1 // self.buffer_size      #返回商的整数部分,向下取整
        location_id = action1 % self.buffer_size        #取模,返回一个返回除法的余数
        return relevant_id, location_id                  #表达一个是用户相关的哪个F-AP,一个是在这个F-AP中的哪个位置


    def execute(self, action, ue_id, slot):        #执行动作
        relevant_id, location_id = self.action_transaction(action)   #将上面函数的返回值赋予下面此处,从而确定与用户相关的F-AP的id,以及用户的位置
        ap_id = self.ue[ue_id].relevant[relevant_id]       #赋予用户相关联的F-AP的序列号
        # if interval_id<self.time_slots-6:
        #     reward2 = self.ap_hit_rate(interval_id + 1)
        #     reward3 = self.ap_hit_rate(interval_id+2)+self.ap_hit_rate(interval_id+3)+self.ap_hit_rate(interval_id+4)+self.ap_hit_rate(interval_id+5)+self.ap_hit_rate(interval_id+6)
        #     reward1 = reward2+0.2*reward3
        # else:
        #     reward1 = self.ap_hit_rate(interval_id + 1)
        reward1 = self.hit_num(slot + 1)                                                                  #奖励,奖励的含义是下一次命中反馈得到奖励
        self.ap[ap_id].cache_buffer.update(change_id=location_id, content=self.ue[ue_id].request[slot])   #让用户关联的F-AP进行文件更新
        # print(self.ap[ap_id].cache_buffer.cache)
        self.observe(step=slot, ue_id=(ue_id+1)%self.ue_num)  #时隙变成这个,并且进行下一个用户的,执行动作是获得下一个时隙的各个状态
        # self.observe(step=interval_id, ue_iid=ue_id)
        return np.array(self.observation), reward1     #执行动作,返回状态与奖励


    def observe(self, step, ue_id):      #状态,还是观察总存储文件?
        """

        step: 时隙索引
        ue_id: 用户的ID

        """
        ob_steps = self.ob_steps   #总的状态值
        self.request_frequency1 = np.zeros((self.relevant_num * self.buffer_size), dtype=np.int_)   #定义了用户所能请求的最大容量,以及其在这个容量内请求的次数。向量
        self.request_sorted = np.zeros((2, self.relevant_num * self.buffer_size), dtype=np.int_)   #定义了,第0行是请求文件索引,第1行是请求概率
        if step < ob_steps:           #执行的状态小于总状态值时,即一定要执行这么多词
            past_request = np.zeros(self.ue_num * (step))   #过去的请求的文件
            for m in range(self.ue_num):         #遍历每一个用户
                past_request[m*(step):(m+1)*(step)] = np.copy(self.ue[m].request[0:step])   #让过去请求的文件进行一个总和,放在同一个矩阵中
            for i in range(self.relevant_num):       #遍历用户相关联的F-AP
                for j in range(len(self.ap[self.ue[ue_id].relevant[i]].cache_buffer.cache)): #遍历该用户的F-AP的缓存文件
                    for k in range(self.ue_num * (step)):            #在上一个循环的基础上,锁定该用户,并遍历用户的每一个时隙(请求频率是从总体用户层面上看一个文件请求的次数吧)
                        # if self.ap[i].cache_buffer.cache[j] == self.all_request[k]:
                        if self.ap[self.ue[ue_id].relevant[i]].cache_buffer.cache[j] == past_request[k]:   #如果该用户所关联的F-AP的内容与过去请求的文件(分别与过去的每一个时隙中,所有用户所请求的文件进行对比)相同
                            self.request_frequency1[i*self.buffer_size+j] += 1     #请求的频率加1,i*self.buffer_size决定了是哪个F-AP,j则决定了是这个F-AP中的第几个序列,就可以看出哪个文件请求频率高,也是从总体的层面上,去看一个文件的请求次数、

        else:
            past_request = np.zeros(self.ue_num * ob_steps)       #此时,执行的次数要大于总状态值了,让过去的请求等于空集
            for m in range(self.ue_num):                          #遍历每一个用户
                past_request[m * (ob_steps):(m + 1) * (ob_steps)] = np.copy(self.ue[m].request[step-ob_steps:step])   #把每个用户的请求总和起来,step-ob_steps好像有问题?
            for i in range(self.relevant_num):                                                                         #遍历每一个相关联的F-AP
                for j in range(len(self.ap[self.ue[ue_id].relevant[i]].cache_buffer.cache)):                           #遍历每一个用户关联的每一个F-AP中所储存的每一个文件,其中len是返回长度的意思
                    for k in range(self.ue_num * ob_steps):                                                            #遍历所有用户的调度
                        if self.ap[self.ue[ue_id].relevant[i]].cache_buffer.cache[j] == past_request[k]:               #查看每一个用户的每一个F-AP中的各个文件是否等于所有用户请求的文件。(这里是不是都点非常规了,这样的话就是看只观察总体命中的情况了)
                            self.request_frequency1[i*self.buffer_size+j] += 1                                         #从总体上查看一个文件的请求次数,但前提是已经缓存于系统中的。

        tmp_a = sorted(enumerate(self.request_frequency1), key=lambda x: x[1])                                        #enumerate创建索引,有多种用法,sort是对迭代完的对象进行一个排序,key=lambda x: x[1]代表enumerate中出来的索引以及内容从小到大进行排序
        for p in range(len(self.request_frequency1)):                                                                #对所有缓存文件数量进行一个遍历
            self.request_sorted[0][p] = tmp_a[p][0]                                                                  #让sort中的第一行作为文件的记录,enumerate是纵向
            self.request_sorted[1][p] = tmp_a[p][1]                                                                  #让sort中的第二行作为文件申请频率的记录,记录为从小到达
        for m in range(len(self.request_frequency1)):                                                                #对缓存文件数量进行一个遍历,一个循环
            self.observation[2 * m] = self.request_sorted[1][m] / ob_steps                                          #将请求次数去除以总请求次数,进行归一化处理,随后放入observation中的偶数项中,作为观察
            
            
            
    def fifo(self):                                                                                               #用户请求文件时,其关联的F-AP是没有该文件后,向上级请求从而增加该文件的过程
        number = np.zeros(self.time_slots, dtype= float)
        judge=0
        ch=0                                                                                                        #用户变换位置的次数
        for i in range(self.time_slots):                                                                             #遍历每一次调度时隙,即总共请求次数,对于for,谁前谁后,谁在谁中间怎么看?
            number[i] = self.hit_num(slot=i)                                                                         #将这个调度时隙中,命中的次数赋予number中

            if i % self.duration == 0:                                                                               #没过一个duration的调度间隔后,对用户周围的F-AP进行一个变动
                self.assign_relevant(ch)                                                                            #变换每个用户关联的F-AP
                ch+=1                                                                                               #变换次数增加
                print(self.ue[0].relevant)                                                                           #将一个用户的关联列出来?
            for j in range(self.ue_num):                                                                            #遍历每一个用户
                if not self.all_request[j*self.time_slots+i] in self.ap[self.ue[j].relevant[0]].cache_buffer.cache:  #该用户的请求文件不在其关联的其中F-AP中有缓存,则往下
                    if not self.all_request[j*self.time_slots+i] in self.ap[self.ue[j].relevant[1]].cache_buffer.cache:  #如果该用户的请求文件不在其关联的编号为0的F-AP中有缓存,则往下
                        if not self.ap[self.ue[j].relevant[0]].cache_buffer.is_full():                                     #该用户关联的其中一个F-AP缓存的文件容量没有达到F-AP最大的缓存容量
                            self.ap[self.ue[j].relevant[0]].cache_buffer.add_cache(self.all_request[j*self.time_slots+i])  #把用户请求的文件加到这个F-AP中
                        elif not self.ap[self.ue[j].relevant[1]].cache_buffer.is_full():                                   #那如果该用户请求的文件不再编号为1的F-AP中有缓存,则往下
                           self.ap[self.ue[j].relevant[1]].cache_buffer.add_cache(self.all_request[j*self.time_slots+i])    #把该文件加到这个F-AP中
                        # elif not self.ap[self.ue[i].relevant[2]].cache_buffer.is_full():
                        #     self.ap[self.ue[i].relevant[2]].cache_buffer.add_cache(self.ue[i].all_request[j])
                        else:                                                                                              #对上面的if的反面
                            self.ap[self.ue[j].relevant[judge]].cache_buffer.add_cache(self.all_request[j*self.time_slots+i])  #把第一个文件删除,然后在最后加入这个文件,在F-AP0中进行
        print(number)                                                                                                              #写出命中矩阵,即每个时隙命中的情况
        # self.observe()
        return number                                                                                                           #返回一个命中值
    
    
    
    def lru(self):                                                                    #对比算法?
        number = np.zeros(self.time_slots, dtype=float)
        judge = 0
        ch=0
        for i in range(self.time_slots):
            number[i] = self.hit_num(slot=i)

            if i % self.duration == 0:
                self.assign_relevant(ch)
                ch+=1
                print(self.ue[0].relevant)                                            #与上面一样,是否专门用于对比?
            for j in range(self.ue_num):
                if self.all_request[j * self.time_slots + i] in self.ap[self.ue[j].relevant[0]].cache_buffer.cache:     #判断每个用户请求的第i个文件是否在其关联的编号为0的F-AP上有缓存
                    self.ap[self.ue[j].relevant[0]].cache_buffer.resort(self.all_request[j * self.time_slots + i])      #把请求的文件进行一个先后排序?
                    judge = 0                                                                                           #判断文件在编号0的F-AP这个位置
                if self.all_request[j * self.time_slots + i] in self.ap[self.ue[j].relevant[1]].cache_buffer.cache:     #判断每个用户请求的第i个文件是否在其关联的编号为0的F-AP上有缓存
                    self.ap[self.ue[j].relevant[1]].cache_buffer.resort(self.all_request[j * self.time_slots + i])      #把请求的文件进行一个先后排序?
                    judge = 1                                                                                           #判断在1这个位置
                if not self.all_request[j * self.time_slots + i] in self.ap[self.ue[j].relevant[0]].cache_buffer.cache:    #判断,当请求文件不再编号为0的F-AP缓存中
                    if not self.all_request[j * self.time_slots + i] in self.ap[self.ue[j].relevant[1]].cache_buffer.cache: #判断,当亲求文件也不在编号为1的F-AP中
                        if not self.ap[self.ue[j].relevant[0]].cache_buffer.is_full():                                     #与上面一样
                            self.ap[self.ue[j].relevant[0]].cache_buffer.add_cache(self.all_request[j * self.time_slots + i])  #与上面一样
                        elif not self.ap[self.ue[j].relevant[1]].cache_buffer.is_full():                                       #与上面一样
                            self.ap[self.ue[j].relevant[1]].cache_buffer.add_cache(self.all_request[j * self.time_slots + i])   #与上面一样
                        else:
                            self.ap[self.ue[j].relevant[judge]].cache_buffer.add_cache(self.all_request[j * self.time_slots + i])  #把第一个文件删除,然后在最后加入这个文件,在F-AP0中进行
        print(number)
        return number




    def lfu(self):
        hit_num = np.zeros((self.ap_num, self.buffer_size))                                                             #定义了一个命中的矩阵,是2维的,(究竟是一个什么样子的矩阵?)但是根据下面出现的情况来看,不能存在重复缓存的文件,要不会出现资源浪费
        number = np.zeros(self.time_slots, dtype=float)
        ch=0
        for i in range(self.time_slots):

            number[i] = self.hit_num(slot=i)                                                                            #与上面一样,应该也是用于对比
            if i % self.duration == 0:
                self.assign_relevant(ch)                                                                                #重新指派每个用户周围的F-AP
                ch+=1                                                                                                   #变化次数加一
                print(self.ue[0].relevant)                                                                              #?把第一个用户的相关联的F-AP输出?
            for j in range(self.ue_num):                                                                                #遍历每一个用户
                if self.ue[j].request[i] in self.ap[self.ue[j].relevant[0]].cache_buffer.cache:                         #如果用户的请求文件在与用户关联的编号为0的F-AP缓存中
                    index_tmp = self.ap[self.ue[j].relevant[0]].cache_buffer.cache.index(self.ue[j].request[i])         #查找编号为0的F-AP中首次出现用户想要请求的文件的位置
                    hit_num[self.ue[j].relevant[0]][index_tmp] += 1                                                     #命中次数加一,此处的命中给了详细的位置,并且没有去调用函数,单纯增加
                else:
                    if self.ue[j].request[i] in self.ap[self.ue[j].relevant[1]].cache_buffer.cache:                     #如果用户的请求文件在与用户关联的编号为1的F-AP的缓存中
                        index_tmp = self.ap[self.ue[j].relevant[1]].cache_buffer.cache.index(self.ue[j].request[i])     #用index函数找出编号为1的F-AP中首次出现用户想要请求的文件的位置
                        hit_num[self.ue[j].relevant[1]][index_tmp] += 1                                                 #对这个命中+1,设定的命中没有调用函数,且对命中的位置进行了定位ue[j].relevant[1]]确定是哪个F-AP,后面的确定是这个F-AP的哪个位置
                    else:                                                                                               #当所请求的文件都不在与用户关联的F-AP的缓存空集中,往下走
                        if not self.ap[self.ue[j].relevant[0]].cache_buffer.is_full():                                  #判断编号为0的F-AP的缓存空间是否没满
                            self.ap[self.ue[j].relevant[0]].cache_buffer.add_cache(self.ue[j].request[i])               #没满,则把用户请求的文件加入编号为0的F-AP中
                            index_tmp = self.ap[self.ue[j].relevant[0]].cache_buffer.cache.index(self.ue[j].request[i]) #在文件缓存后,查找出缓存的位置
                            hit_num[self.ue[j].relevant[0]][index_tmp] = 1                                              #在定义的命中矩阵的相应位置上加1,代表命中次数加一
                        else:                                                                                           #如果F-AP0空间满了
                            if not self.ap[self.ue[j].relevant[1]].cache_buffer.is_full():                              #判断是否F-AP1的空间是否没满
                                self.ap[self.ue[j].relevant[1]].cache_buffer.add_cache(self.ue[j].request[i])           #没满则把请求文件加入这里
                                index_tmp = self.ap[self.ue[j].relevant[1]].cache_buffer.cache.index(self.ue[j].request[i])  #并把加入的位置记录下来
                                hit_num[self.ue[j].relevant[1]][index_tmp] = 1                                          #在命中空间相应的空间中记录以命中
                            else:                                                                                       #如果既没有命中,且F-APs的空间都已经满了
                                a = hit_num[self.ue[j].relevant[0]].min()                                               #找到F-AP0中命中次数最多的值
                                b = hit_num[self.ue[j].relevant[1]].min()                                               #找到F-AP1中命中次数最少的值
                                if a < b:                                                                               #判断谁最小,最小的往下
                                    c = np.argmin(hit_num[self.ue[j].relevant[0]])                                      #把这个命中次数最小的下标,或者说文件找到,赋予c
                                    ap_id = self.ue[j].relevant[0]                                                      #并标记好命中次数最少的文件所在地是F-AP0中
                                else: #a >= b
                                    c = np.argmin(hit_num[self.ue[j].relevant[1]])                                      #同上,找出对应的下标
                                    ap_id = self.ue[j].relevant[1]                                                      #并标记好最少的是在F-AP1中
                                self.ap[ap_id].cache_buffer.cache[c] = self.ue[j].request[i]                            #把这个请求最少的文件给替换成用户请求的文件
                                hit_num[self.ue[j].relevant[1]][c] = 1                                                  #在命中矩阵中,让这个位置命中加一

        print(number)                                                                                                   #打出总命中次数
        return number

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值