def main(config):
    TAU_F = config['TAU_F']
    W_FF = config['W_FF']
    V_TH = config['V_TH']
    G = config['G']
    V_0 = config['V_0']
    DS = config['DS']
    
    FONT_SIZE = config['FONT_SIZE']
    FIG_SIZE = config['FIG_SIZE']
    
    def f(v, d):
        return (1 / TAU_F) * (-v + W_FF*phi(G*(v - V_TH)) + V_0 + d)
    
    vs = np.linspace(-6, 10, 200)
    fs = [f(vs, d) for d in DS]

    fig, ax = plt.subplots(1, 1, figsize=FIG_SIZE)
    ax.set_xlabel('v', fontsize=16)
    ax.set_ylabel('f(v)', fontsize=16)
    ax.plot(vs, np.transpose(fs), lw=2)
    ax.legend(['d = {}'.format(d) for d in DS], loc='best')
    ax.axvline(0, ls='--', lw=1, c='k')
    ax.axhline(0, ls='--', lw=1, c='k')
    
    axis_tools.set_fontsize(ax, FONT_SIZE)
 def test3_input_populations_coupled_to_bistable_population(self):
     # define network drive
     drive_1 = 10*[0] + 20*[10] + 200*[0]
     drive_2 = 15*[0] + 20*[10] + 195*[0]
     drive_12 = len(drive_1) * [0]
     drives = np.transpose([drive_1, drive_2, drive_12])
     
     # define network parameters
     taus = [10, 10, 10]
     v_rests = [0, 0, 0]
     v_ths = [4, 4, 4]
     gs = [2.5, 2.5, 2.5]
     noise_level = 2
     
     nodes = [{'tau': t, 'v_rest': v_rest, 'threshold': th, 'steepness': g}
             for t, v_rest, th, g in zip(taus, v_rests, v_ths, gs)]
     weights = np.array([
             [0, 0, 0],
             [0, 0, 0],
             [2, 2, 5.5]
         ])
     
     # make network
     ntwk = RateBasedModel(nodes, weights)
     ntwk.store_voltages = True
     ntwk.noise_level = noise_level
     
     # initialize and run network
     np.random.seed(1)
     ntwk.vs = np.array(v_rests)
     for drive in drives:
         ntwk.step(drive)
         
     fig, axs = plt.subplots(3, 1, figsize=(14, 8), sharex=True)
     axs[0].plot(ntwk.vs_history, lw=2)
     axs[1].plot(ntwk.rs_history, lw=2)
     axs[2].plot(drives, lw=2)
     
     axs[1].set_ylim(-.1, 1.1)
     axs[2].set_ylim(-1, 11)
     
     axs[0].set_ylabel('voltage')
     axs[1].set_ylabel('firing rate')
     axs[2].set_ylabel('drive')
     axs[2].set_xlabel('t')
     
     axs[0].set_title('Input populations coupled to bistable population')
     
     for ax in axs:
         axis_tools.set_fontsize(ax, 20)
 def test2_upstate_timecourse_with_self_connections_depends_on_noise(self):
     # define network drive
     drives = 10*[np.array([0, 0, 0])] + 40*[4*np.array([1, 1, 1])] + \
              140*[np.array([0, 0, 0])]
     # define network parameters
     taus = [10, 10, 10]
     v_rests = [0, 0, 0]
     v_ths = [4, 4, 4]
     gs = [2.5, 2.5, 2.5]
     w_selfs = [5.3, 5.3, 5.3]
     noise_level = np.array([1, 2, 3])
     
     nodes = [{'tau': t, 'v_rest': v_rest, 'threshold': th, 'steepness': g}
             for t, v_rest, th, g in zip(taus, v_rests, v_ths, gs)]
     weights = np.diag(w_selfs)
     
     # make network
     ntwk = RateBasedModel(nodes, weights)
     ntwk.store_voltages = True
     ntwk.noise_level = noise_level
     
     # initialize and run network
     np.random.seed(seed=4)
     ntwk.vs = np.array(v_rests)
     for drive in drives:
         ntwk.step(drive)
         
     # make plots
     fig, axs = plt.subplots(3, 1, figsize=(14, 8), sharex=True)
     axs[0].plot(ntwk.vs_history, lw=2)
     axs[1].plot(ntwk.rs_history, lw=2)
     axs[2].plot(drives, lw=2, c='k')
     
     axs[1].set_ylim(-.1, 1.1)
     axs[2].set_ylim(-1, 5)
     
     axs[0].set_ylabel('voltage')
     axs[1].set_ylabel('firing rate')
     axs[2].set_ylabel('drive')
     axs[2].set_xlabel('t')
     
     axs[0].set_title('Self-Connected Populations With Noise')
     
     for ax in axs:
         axis_tools.set_fontsize(ax, 20)
def main():
    # make network
    nodes = [{'tau': TAU, 'v_rest': v_rest, 'threshold': V_TH, 'steepness': G} for v_rest in V_RESTS]
    weights = np.array([
            [0., 0, 0, 0, CWO],
            [0, 0, 0, CWO, 0],
            [FSW, FSW, SRW, 0, 0],
            [CWI, 0, CGW, 0, 0],
            [0, CWI, CGW, 0, 0],
        ])
    ntwk = network.RateBasedModel(nodes, weights)
    ntwk.store_voltages = True
    ntwk.noise_level = NOISE_LEVEL
    
    # set up network drive
    drive_1 = 10*[0] + 20*[10] + 100*[0] + 20*[10] + 80*[0]
    drive_2 = 25*[0] + 20*[10] + 185*[0]
    drive_12s = len(drive_1) * [0]
    drive_12 = len(drive_1) * [0]
    drive_21 = len(drive_1) * [0]
    drives = np.transpose([drive_1, drive_2, drive_12s, drive_12, drive_21])
    t = np.arange(len(drives))
    
    # run network
    np.random.seed(SEED)
    ntwk.vs = np.array(V_RESTS)
    for drive in drives:
        ntwk.step(drive)
    
    # make figure
    fig, axs = plt.subplots(3, 1, facecolor=FACE_COLOR, figsize=FIG_SIZE, sharex=True)
    axs[0].plot(t, ntwk.vs_history, lw=2)
    axs[1].plot(t, ntwk.rs_history, lw=2)
    axs[2].plot(t, drives, lw=2)
    
    axs[0].set_ylabel('voltage')
    axs[1].set_ylabel('firing rate')
    axs[2].set_ylabel('drive')
    axs[2].set_xlabel('t')
    
    axs[0].set_title('Two-component memory network')
    
    for ax in axs:
        axis_tools.set_fontsize(ax, FONT_SIZE)
 def test1_self_connections_yield_bistable_systems_given_correct_parameters(self):
     # define network drive
     drives = 10*[np.array([0, 0])] + 20*[4*np.array([1, 1])] + \
              60*[np.array([0, 0])] + 20*[-4*np.array([1, 1])] + 60*[np.array([0, 0])]
     # define network parameters
     taus = [10, 10]
     v_rests = [0, 0]
     v_ths = [4, 4]
     gs = [2.5, 2.5]
     w_selfs = [7.5, 5.5]
     
     nodes = [{'tau': t, 'v_rest': v_rest, 'threshold': th, 'steepness': g}
             for t, v_rest, th, g in zip(taus, v_rests, v_ths, gs)]
     weights = np.diag(w_selfs)
     
     # make network
     ntwk = RateBasedModel(nodes, weights)
     ntwk.store_voltages = True
     
     # initialize and run network
     ntwk.vs = np.array(v_rests)
     for drive in drives:
         ntwk.step(drive)
         
     # make plots
     fig, axs = plt.subplots(3, 1, figsize=(14, 8), sharex=True)
     axs[0].plot(ntwk.vs_history, lw=2)
     axs[1].plot(ntwk.rs_history, lw=2)
     axs[2].plot(drives, lw=2, c='k')
     
     axs[1].set_ylim(-.1, 1.1)
     axs[2].set_ylim(-5, 5)
     
     axs[0].set_ylabel('voltage')
     axs[1].set_ylabel('firing rate')
     axs[2].set_ylabel('drive')
     axs[2].set_xlabel('t')
     
     axs[0].set_title('Self-Connected Populations')
     
     for ax in axs:
         axis_tools.set_fontsize(ax, 20)
 def test0_unconnected_populations_with_different_timescales_respond_to_delta_pulse_with_exponential_decay(self):
     # define network drive
     drives = 10*[np.array([0, 0, 0, 0])] + 10*[2*np.array([3, 4, 6, 10])] + 60*[np.array([0, 0, 0, 0])]
     # define network parameters
     taus = [3, 5, 10, 20]
     v_rests = [-20, -10, 0, 10]
     v_ths = [-10, -1, 8, 17]
     gs = [.5, .5, .5, .5]
     
     # dake new network
     nodes = [{'tau': t, 'v_rest': v_rest, 'threshold': th, 'steepness': g}
             for t, v_rest, th, g in zip(taus, v_rests, v_ths, gs)]
     weights = np.zeros((len(taus), len(taus)))
     
     ntwk = RateBasedModel(nodes, weights)
     ntwk.store_voltages = True
     
     # initialize and run network
     ntwk.vs = np.array(v_rests)
     for drive in drives:
         ntwk.step(drive)
     
     # make plots
     fig, axs = plt.subplots(3, 1, figsize=(14, 8), sharex=True)
     axs[0].plot(ntwk.vs_history, lw=2)
     axs[1].plot(ntwk.rs_history, lw=2)
     axs[2].plot(drives, lw=2)
     
     axs[1].set_ylim(-.1, 1.1)
     axs[2].set_ylim(0, 45)
     
     axs[0].set_ylabel('voltage')
     axs[1].set_ylabel('firing rate')
     axs[2].set_ylabel('drive')
     axs[2].set_xlabel('t')
     
     axs[0].set_title('Uncoupled Populations')
     
     for ax in axs:
         axis_tools.set_fontsize(ax, 20)
def novel_pattern_learning(CONFIG):
    """
    Show that a network has an increased probability of embedding a novel pattern
    into its connectivity network if we allow nonassociative priming to act.
    """

    SEED = CONFIG["SEED"]

    LOAD_FILE_NAME = CONFIG["LOAD_FILE_NAME"]

    W_WEAK = CONFIG["W_WEAK"]

    GAIN = CONFIG["GAIN"]
    REFRACTORY_STRENGTH = CONFIG["REFRACTORY_STRENGTH"]

    LINGERING_INPUT_VALUE = CONFIG["LINGERING_INPUT_VALUE"]
    LINGERING_INPUT_TIMESCALE = CONFIG["LINGERING_INPUT_TIMESCALE"]

    W_MAX = CONFIG["W_MAX"]
    ALPHA = CONFIG["ALPHA"]

    STRONG_DRIVE_AMPLITUDE = CONFIG["STRONG_DRIVE_AMPLITUDE"]

    RUN_LENGTH = CONFIG["RUN_LENGTH"]

    N_REPEATS = CONFIG["N_REPEATS"]

    FIG_SIZE_0 = CONFIG["FIG_SIZE_0"]
    FONT_SIZE = CONFIG["FONT_SIZE"]

    np.random.seed(SEED)

    # create new network with STDP learning rule
    ntwk_old = np.load(LOAD_FILE_NAME)[0]

    w = ntwk_old.w.copy()

    # add weak connection to element 2 of node_1 path tree from element 1 of node_0 path tree
    w[ntwk_old.node_1_path_tree[0][2], ntwk_old.node_0_path_tree[0][1]] = W_WEAK
    w_to_track = (ntwk_old.node_1_path_tree[0][2], ntwk_old.node_0_path_tree[0][1])
    path_novel = ntwk_old.node_0_path_tree[0][:2] + ntwk_old.node_1_path_tree[0][2:]

    # make new base network
    ntwk_base = network.RecurrentSoftMaxLingeringSTDPModelBasic(
        w, GAIN, REFRACTORY_STRENGTH, LINGERING_INPUT_VALUE, LINGERING_INPUT_TIMESCALE, W_MAX, ALPHA
    )
    ntwk_base.node_0 = ntwk_old.node_0
    ntwk_base.node_0 = ntwk_old.node_1
    ntwk_base.node_0_path_tree = ntwk_old.node_0_path_tree
    ntwk_base.node_1_path_tree = ntwk_old.node_1_path_tree

    # show long trials of novel sequence drive, spontaneous activity, and test sequence
    fig, axs = plt.subplots(N_REPEATS, 1, figsize=FIG_SIZE_0, sharex=True, tight_layout=True)
    axs_twin = [ax.twinx() for ax in axs]

    drives = np.zeros((RUN_LENGTH, ntwk_base.w.shape[0]), dtype=float)
    for ctr, node in enumerate(path_novel):
        drives[ctr, node] = STRONG_DRIVE_AMPLITUDE

    for ax, ax_twin in zip(axs, axs_twin):

        ntwk = deepcopy(ntwk_base)
        ntwk.store_voltages = True

        ws = []

        for drive in drives:
            ntwk.step(drive)
            ws.append(ntwk.w[w_to_track])

        spikes = np.array(ntwk.rs_history)

        fancy_raster.by_row_circles(ax, spikes, drives)

        ax_twin.plot(ws, color="b", lw=2, alpha=0.7)

        ax.set_ylabel("active \n ensemble")
        ax_twin.set_ylabel("W({}, {})".format(*w_to_track), color="b")

    axs[-1].set_xlabel("time step")

    for ax_twin in axs_twin:
        ax_twin.set_ylim(0, 2)

    axs[0].set_xlim(-5, RUN_LENGTH)
    axs[0].set_title("With nonassociative priming")

    for ax in list(axs) + axs_twin:
        axis_tools.set_fontsize(ax, FONT_SIZE)

    fig, axs = plt.subplots(N_REPEATS, 1, figsize=FIG_SIZE_0, sharex=True, tight_layout=True)
    axs_twin = [ax.twinx() for ax in axs]

    drives = np.zeros((RUN_LENGTH, ntwk_base.w.shape[0]), dtype=float)
    for ctr, node in enumerate(path_novel):
        drives[ctr, node] = STRONG_DRIVE_AMPLITUDE

    for ax, ax_twin in zip(axs, axs_twin):

        ntwk = deepcopy(ntwk_base)
        ntwk.lingering_input_value = 0
        ntwk.store_voltages = True

        ws = []

        for drive in drives:
            ntwk.step(drive)
            ws.append(ntwk.w[w_to_track])

        spikes = np.array(ntwk.rs_history)

        fancy_raster.by_row_circles(ax, spikes, drives)

        ax_twin.plot(ws, color="b", lw=2, alpha=0.7)

        ax.set_ylabel("active \n ensemble")
        ax_twin.set_ylabel("W({}, {})".format(*w_to_track), color="b")

    axs[-1].set_xlabel("time step")

    for ax_twin in axs_twin:
        ax_twin.set_ylim(0, 2)

    axs[0].set_xlim(-5, RUN_LENGTH)
    axs[0].set_title("Without nonassociative priming")

    for ax in list(axs) + axs_twin:
        axis_tools.set_fontsize(ax, FONT_SIZE)
Beispiel #8
0
def main(config):
    SEED = config['SEED']
    
    TAU_E = config['TAU_E']
    TAU_I = config['TAU_I']
    V_REST_E = config['V_REST_E']
    V_REST_I = config['V_REST_I']
    V_TH = config['V_TH']
    STEEPNESS = config['STEEPNESS']
    
    W_EE = config['W_EE']
    W_EI = config['W_EI']
    W_IE = config['W_IE']
    
    NOISE_LEVEL = config['NOISE_LEVEL']
    
    DRIVE_AMPS = config['DRIVE_AMPS']
    DRIVE_STARTS = config['DRIVE_STARTS']
    DRIVE_ENDS = config['DRIVE_ENDS']
    
    DURATION = config['DURATION']
    
    FIG_SIZE = config['FIG_SIZE']
    FONT_SIZE = config['FONT_SIZE']
    
    COLOR_CYCLE = config['COLOR_CYCLE']
    
    # set up nodes and weights
    nodes = [
        {'tau': TAU_E, 'v_rest': V_REST_E, 'threshold': V_TH, 'steepness': STEEPNESS,},
        {'tau': TAU_I, 'v_rest': V_REST_I, 'threshold': V_TH, 'steepness': STEEPNESS,},
    ]
    weights = np.array([
        [W_EE, W_EI],
        [W_IE,  0.0],
    ])
    
    # build network
    ntwk = network.RateBasedModel(nodes, weights)
    ntwk.store_voltages = True
    ntwk.noise_level = NOISE_LEVEL
    
    # set up drive
    drives = np.zeros((DURATION, len(nodes)), dtype=float)
    for drive_amp, drive_start, drive_end in zip(DRIVE_AMPS, DRIVE_STARTS, DRIVE_ENDS):
        drives[drive_start:drive_end, 0] = drive_amp
        
    # run network
    np.random.seed(SEED)
    ntwk.vs = np.array([V_REST_E, V_REST_I])
    for drive in drives:
        ntwk.step(drive)
        
    # make figure
    fig, axs = plt.subplots(3, 1, figsize=FIG_SIZE, sharex=True, tight_layout=True)
    axs_twin = [ax.twinx() for ax in axs[:2]]
    
    axs[0].plot(np.array(ntwk.vs_history)[:, 0], c=COLOR_CYCLE[0], ls='--', lw=2)
    axs_twin[0].plot(np.array(ntwk.rs_history)[:, 0], c=COLOR_CYCLE[0], ls='-', lw=2)
    
    axs[1].plot(np.array(ntwk.vs_history)[:, 1], c=COLOR_CYCLE[1], ls='--', lw=2)
    axs_twin[1].plot(np.array(ntwk.rs_history)[:, 1], c=COLOR_CYCLE[1], ls='-', lw=2)

    axs[2].set_color_cycle(COLOR_CYCLE)
    axs[2].plot(drives, lw=2)
    
    axs_twin[0].set_ylim(0, 1)
    axs_twin[1].set_ylim(0, 1)
    axs[2].set_ylim(0, drives.max() * 1.1)
    
    axs[0].set_title('Excitatory')
    axs[0].set_ylabel('Voltage')
    axs_twin[0].set_ylabel('Firing rate')
    
    axs[1].set_title('Inhibitory')
    axs[1].set_ylabel('Voltage')
    axs_twin[1].set_ylabel('Firing rate')
    
    axs[2].set_title('Drive')
    axs[2].set_ylabel('Drive')
    axs[2].set_xlabel('t')
    
    for ax in list(axs) + list(axs_twin):
        axis_tools.set_fontsize(ax, FONT_SIZE)
def main(config):
    # unpack params from config
    SEED = config['SEED']
    
    TAU_I = config['TAU_I']
    TAU_R = config['TAU_R']
    TAU_B = config['TAU_B']
    TAU_A = config['TAU_A']
    V_REST_I = config['V_REST_I']
    V_REST_R = config['V_REST_R']
    V_REST_B = config['V_REST_B']
    V_REST_A = config['V_REST_A']
    STEEPNESS = config['STEEPNESS']
    THRESHOLD = config['THRESHOLD']
    
    W_IB = config['W_IB']
    W_BI = config['W_BI']
    W_BR = config['W_BR']
    W_AB = config['W_AB']
    W_AA = config['W_AA']
    
    NOISE_LEVEL = config['NOISE_LEVEL']
    
    DURATION = config['DURATION']
    
    DRIVE_11_START = config['DRIVE_11_START']
    DRIVE_11_END = config['DRIVE_11_END']
    DRIVE_11_AMP = config['DRIVE_11_AMP']
    
    DRIVE_12_START = config['DRIVE_12_START']
    DRIVE_12_END = config['DRIVE_12_END']
    DRIVE_12_AMP = config['DRIVE_12_AMP']
    
    DRIVE_13_START = config['DRIVE_13_START']
    DRIVE_13_END = config['DRIVE_13_END']
    DRIVE_13_AMP = config['DRIVE_13_AMP']
    
    DRIVE_21_START = config['DRIVE_21_START']
    DRIVE_21_END = config['DRIVE_21_END']
    DRIVE_21_AMP = config['DRIVE_21_AMP']
    
    COLOR_CYCLE = config['COLOR_CYCLE']
    FIG_SIZE = config['FIG_SIZE']
    FONT_SIZE = config['FONT_SIZE']
    
    # set up nodes
    # order: (I, R1, R2, R3, R4, B12, B13, B14, B23, B24, B34, A12, A13, A14, A23, A24, A34)
    nodes = [
        {'tau': TAU_I, 'v_rest': V_REST_I, 'threshold': THRESHOLD, 'steepness': STEEPNESS},  # I
        {'tau': TAU_R, 'v_rest': V_REST_R, 'threshold': THRESHOLD, 'steepness': STEEPNESS},  # R1
        {'tau': TAU_R, 'v_rest': V_REST_R, 'threshold': THRESHOLD, 'steepness': STEEPNESS},  # R2
        {'tau': TAU_R, 'v_rest': V_REST_R, 'threshold': THRESHOLD, 'steepness': STEEPNESS},  # R3
        {'tau': TAU_R, 'v_rest': V_REST_R, 'threshold': THRESHOLD, 'steepness': STEEPNESS},  # R4
        {'tau': TAU_B, 'v_rest': V_REST_B, 'threshold': THRESHOLD, 'steepness': STEEPNESS},  # B12
        {'tau': TAU_B, 'v_rest': V_REST_B, 'threshold': THRESHOLD, 'steepness': STEEPNESS},  # B13
        {'tau': TAU_B, 'v_rest': V_REST_B, 'threshold': THRESHOLD, 'steepness': STEEPNESS},  # B14
        {'tau': TAU_B, 'v_rest': V_REST_B, 'threshold': THRESHOLD, 'steepness': STEEPNESS},  # B23
        {'tau': TAU_B, 'v_rest': V_REST_B, 'threshold': THRESHOLD, 'steepness': STEEPNESS},  # B24
        {'tau': TAU_B, 'v_rest': V_REST_B, 'threshold': THRESHOLD, 'steepness': STEEPNESS},  # B34
        {'tau': TAU_A, 'v_rest': V_REST_A, 'threshold': THRESHOLD, 'steepness': STEEPNESS},  # A12
        {'tau': TAU_A, 'v_rest': V_REST_A, 'threshold': THRESHOLD, 'steepness': STEEPNESS},  # A13
        {'tau': TAU_A, 'v_rest': V_REST_A, 'threshold': THRESHOLD, 'steepness': STEEPNESS},  # A14
        {'tau': TAU_A, 'v_rest': V_REST_A, 'threshold': THRESHOLD, 'steepness': STEEPNESS},  # A23
        {'tau': TAU_A, 'v_rest': V_REST_A, 'threshold': THRESHOLD, 'steepness': STEEPNESS},  # A24
        {'tau': TAU_A, 'v_rest': V_REST_A, 'threshold': THRESHOLD, 'steepness': STEEPNESS},  # A34
    ]
    
    # set up weight matrix
    weights = np.array([
        #  I    R1    R2    R3    R4    B12   B13   B14   B23   B24   B34   A12   A13   A14   A23   A24   A34
        [ 0.0,  0.0,  0.0,  0.0,  0.0, W_IB, W_IB, W_IB,  W_IB, W_IB, W_IB, 0.0,  0.0,  0.0,  0.0,  0.0,  0.0,],  # I
        [ 0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,],  # R1
        [ 0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,],  # R2
        [ 0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,],  # R3
        [ 0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,],  # R4
        [W_BI, W_BR, W_BR,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,],  # B12
        [W_BI, W_BR,  0.0, W_BR,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,],  # B13
        [W_BI, W_BR,  0.0,  0.0, W_BR,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,],  # B14
        [W_BI,  0.0, W_BR, W_BR,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,],  # B23
        [W_BI,  0.0, W_BR,  0.0, W_BR,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,],  # B24
        [W_BI,  0.0,  0.0, W_BR, W_BR,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,],  # B34
        [ 0.0,  0.0,  0.0,  0.0,  0.0, W_AB,  0.0,  0.0,  0.0,  0.0,  0.0, W_AA,  0.0,  0.0,  0.0,  0.0,  0.0,],  # A12
        [ 0.0,  0.0,  0.0,  0.0,  0.0,  0.0, W_AB,  0.0,  0.0,  0.0,  0.0,  0.0, W_AA,  0.0,  0.0,  0.0,  0.0,],  # A13
        [ 0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0, W_AB,  0.0,  0.0,  0.0,  0.0,  0.0, W_AA,  0.0,  0.0,  0.0,],  # A14
        [ 0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0, W_AB,  0.0,  0.0,  0.0,  0.0,  0.0, W_AA,  0.0,  0.0,],  # A23
        [ 0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0, W_AB,  0.0,  0.0,  0.0,  0.0,  0.0, W_AA,  0.0,],  # A24
        [ 0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0, W_AB,  0.0,  0.0,  0.0,  0.0,  0.0, W_AA,],  # A34
        #  I    R1    R2    R3    R4    B12   B13   B14   B23   B24   B34   A12   A13   A14   A23   A24   A34
    ])
    
    # build network
    ntwk = network.RateBasedModel(nodes, weights)
    ntwk.store_voltages = True
    ntwk.noise_level = NOISE_LEVEL
    
    # set up drive
    drives = np.zeros((DURATION, len(nodes)), dtype=float)
    drives[DRIVE_11_START:DRIVE_11_END, 1] = DRIVE_11_AMP
    drives[DRIVE_12_START:DRIVE_12_END, 1] = DRIVE_12_AMP
    drives[DRIVE_13_START:DRIVE_13_END, 1] = DRIVE_13_AMP
    drives[DRIVE_21_START:DRIVE_21_END, 2] = DRIVE_21_AMP
    
    # run network
    np.random.seed(SEED)
    ntwk.vs = np.array([node['v_rest'] for node in nodes])
    for drive in drives:
        ntwk.step(drive)


    # open a figure
    fig, axs = plt.subplots(4, 1, figsize=FIG_SIZE, sharex=True, tight_layout=True)
    axs_twinx = [None] + [ax.twinx() for ax in axs[1:]]
    
    # plot drive
    axs[0].set_color_cycle(COLOR_CYCLE)
    axs[0].plot(drives[:, 1:5], lw=2)
    
    # plot responses of receptive field (R) units
    axs[1].set_color_cycle(COLOR_CYCLE)
    axs_twinx[1].set_color_cycle(COLOR_CYCLE)
    axs[1].plot(np.array(ntwk.vs_history)[:, 1:5], ls='--', lw=2)
    axs_twinx[1].plot(np.array(ntwk.rs_history)[:, 1:5], ls='-', lw=2)
    
    # plot responses of buffer (B) units
    axs[2].set_color_cycle(COLOR_CYCLE[4:])
    axs_twinx[2].set_color_cycle(COLOR_CYCLE[4:])
    axs[2].plot(np.array(ntwk.vs_history)[:, 5:11], ls='--', lw=2)
    axs_twinx[2].plot(np.array(ntwk.rs_history)[:,5:11], ls='-', lw=2)
    
    # plot response of inhibitory (I) unit
    axs[2].plot(np.array(ntwk.vs_history)[:, 0], ls='--', lw=2, color='k')
    axs_twinx[2].plot(np.array(ntwk.rs_history)[:, 0], ls='-', lw=2, color='k')
    
    # plot response of association (A) units
    axs[3].set_color_cycle(COLOR_CYCLE[4:])
    axs_twinx[3].set_color_cycle(COLOR_CYCLE[4:])
    axs[3].plot(np.array(ntwk.vs_history)[:, 11:], ls='--', lw=2)
    axs_twinx[3].plot(np.array(ntwk.rs_history)[:, 11:], ls='-', lw=2)
    
    # set axis limits
    for ax in axs_twinx[1:]:
        ax.set_ylim(0, 1)
        
    # label things
    axs[0].set_title('drive')
    axs[0].set_ylabel('strength')
    
    axs[1].set_title('receptive field units')
    axs[1].set_ylabel('voltage')
    axs_twinx[1].set_ylabel('firing rate')
    
    axs[2].set_title('buffer units')
    axs[2].set_ylabel('voltage')
    axs_twinx[2].set_ylabel('firing rate')
    
    axs[3].set_title('association units')
    axs[3].set_ylabel('voltage')
    axs_twinx[3].set_ylabel('firing rate')
    
    axs[3].set_xlabel('time')
    
    for ax in list(axs) + axs_twinx[1:]:
        axis_tools.set_fontsize(ax, FONT_SIZE)
def main(config):
    # unpack params from config
    SEED = config['SEED']
    
    TAU = config['TAU']
    TAU_M = config['TAU_M']
    TAU_C = config['TAU_C']
    V_REST = config['V_REST']
    V_REST_C = config['V_REST_C']
    V_TH = config['V_TH']
    STEEPNESS = config['STEEPNESS']
    
    W_IF = config['W_IF']
    W_FS = config['W_FS']
    W_FI = config['W_FI']
    W_FF = config['W_FF']
    W_FC = config['W_FC']
    
    W_MF = config['W_MF']
    W_MM = config['W_MM']
    W_CF = config['W_CF']
    W_CM = config['W_CM']
    
    N_UNITS = config['N_UNITS']
    
    NOISE_LEVEL = config['NOISE_LEVEL']
    S_DRIVE_AMP = config['S_DRIVE_AMP']
    F0_DRIVE_AMP = config['F0_DRIVE_AMP']
    F1_DRIVE_AMP = config['F1_DRIVE_AMP']
    
    T_F0_DRIVE = config['T_F0_DRIVE']
    D_F0_DRIVE = config['D_F0_DRIVE']
    T_F1_DRIVE = config['T_F1_DRIVE']
    D_F1_DRIVE = config['D_F1_DRIVE']
    T2_F1_DRIVE = config['T2_F1_DRIVE']
    D2_F1_DRIVE = config['D2_F1_DRIVE']
    T2_F0_DRIVE = config['T2_F0_DRIVE']
    D2_F0_DRIVE = config['D2_F0_DRIVE']
    T_S_DRIVE = config['T_S_DRIVE']
    
    DURATION = config['DURATION']
    
    FONT_SIZE = config['FONT_SIZE']
    COLOR_CYCLE = config['COLOR_CYCLE']
    
    # generate network nodes and weights using helper function
    nodes, weights = network_param_gen.wta_memory_combo(
        n_units=N_UNITS,
        tau=TAU, tau_m=TAU_M, tau_c=TAU_C, v_rest=V_REST, v_rest_c=V_REST_C, v_th=V_TH, steepness=STEEPNESS,
        w_if=W_IF, w_fs=W_FS, w_fi=W_FI, w_ff=W_FF, w_fc=W_FC, w_mf=W_MF, w_mm=W_MM, w_cf=W_CF, w_cm=W_CM,
    )
    
    # the order of the neurons in this network is:
    # s, i, f, f, ..., f, f, m, c, c, m, c, c, ..., m, c, c, m, c, c
    # there are N_UNITS f neurons, and N_UNITS*(N_UNITS-1)/2 m, c, c groups
    ntwk = network.RateBasedModel(nodes, weights)
    ntwk.noise_level = NOISE_LEVEL
    ntwk.store_voltages = True
    
    # setup network drive
    s_drive = np.zeros((DURATION, 1), dtype=float)
    s_drive[T_S_DRIVE:, 0] = S_DRIVE_AMP
    f_drive = np.zeros((DURATION, N_UNITS), dtype=float)
    f_drive[T_F0_DRIVE:T_F0_DRIVE+D_F0_DRIVE, 0] = F0_DRIVE_AMP
    f_drive[T_F1_DRIVE:T_F1_DRIVE+D_F1_DRIVE, 1] = F1_DRIVE_AMP
    f_drive[T2_F1_DRIVE:T2_F1_DRIVE+D2_F1_DRIVE, 1] = F1_DRIVE_AMP
    f_drive[T2_F0_DRIVE:T2_F0_DRIVE+D2_F0_DRIVE, 0] = F0_DRIVE_AMP
    
    i_drive = np.zeros((DURATION, 1), dtype=float)
    mcc_drive = np.zeros((DURATION, 3*N_UNITS*(N_UNITS-1)/2), dtype=float)
    
    drives = np.concatenate([s_drive, i_drive, f_drive, mcc_drive], axis=1)
    
    # run simulation
    np.random.seed(SEED)
    ntwk.vs = np.array([n['v_rest'] for n in nodes])
    for drive in drives:
        ntwk.step(drive)
    
    # do some things before making figures
    rs = np.array(ntwk.rs_history)
    vs = np.array(ntwk.vs_history)
    
    f_idxs = np.arange(2, 2 + N_UNITS, dtype=int)
    m_idxs = np.arange(2 + N_UNITS, 2 + N_UNITS + 3 * N_UNITS * (N_UNITS - 1) / 2, 3, dtype=int)
    
    fig, axs = plt.subplots(4, 1, figsize=(15, 12), sharex=True, tight_layout=True)
    axs[3].twin = axs[3].twinx()
    for ax in np.concatenate([axs.flatten(), [axs[3].twin]]):
        ax.set_color_cycle(COLOR_CYCLE)
        
    axs[0].plot(rs[:, f_idxs], lw=2)
    axs[1].plot(drives[:, np.concatenate([f_idxs, [0]])], lw=2)
    axs[2].plot(vs[:, m_idxs], lw=2)
    axs[3].plot(rs[:, range(m_idxs[0] + 1, m_idxs[0] + 3)], lw=2, ls='-')
    
    axs[3].twin.plot(vs[:, range(m_idxs[0] + 1, m_idxs[0] + 3)], lw=2, ls='--')
    
    axs[0].set_title('Fast units')
    axs[0].set_ylabel('Firing rate')
    axs[1].set_title('Drive')
    axs[1].set_ylabel('Drive')
    axs[2].set_title('Memory units')
    axs[2].set_ylabel('Voltage')
    axs[3].set_title('Conduit units')
    axs[3].set_ylabel('Firing rate')
    axs[3].twin.set_ylabel('Voltage')
    axs[3].set_xlabel('t')
    
    for ax in np.concatenate([axs.flatten(), [axs[3].twin]]):
        axis_tools.set_fontsize(ax, FONT_SIZE)
def novel_pattern_replay(CONFIG):
    """
    Show how a network that has weak connections in addition to its strong connections can learn to replay novel patterns that would not normally arise spontaneously.
    """

    SEED = CONFIG['SEED']

    LOAD_FILE_NAME = CONFIG['LOAD_FILE_NAME']

    W_WEAK = CONFIG['W_WEAK']

    GAIN = CONFIG['GAIN']
    REFRACTORY_STRENGTH = CONFIG['REFRACTORY_STRENGTH']

    LINGERING_INPUT_VALUE = CONFIG['LINGERING_INPUT_VALUE']
    LINGERING_INPUT_TIMESCALE = CONFIG['LINGERING_INPUT_TIMESCALE']

    STRONG_DRIVE_AMPLITUDE = CONFIG['STRONG_DRIVE_AMPLITUDE']
    WEAK_DRIVE_AMPLITUDE = CONFIG['WEAK_DRIVE_AMPLITUDE']

    TRIAL_LENGTH_TRIGGERED_REPLAY = CONFIG['TRIAL_LENGTH_TRIGGERED_REPLAY']
    RUN_LENGTH = CONFIG['RUN_LENGTH']

    FIG_SIZE_0 = CONFIG['FIG_SIZE_0']
    FIG_SIZE_1 = CONFIG['FIG_SIZE_1']

    FONT_SIZE = CONFIG['FONT_SIZE']

    np.random.seed(SEED)

    fig = plt.figure(figsize=FIG_SIZE_0, tight_layout=True)
    axs = []
    for row_ctr in range(3):
        axs.append([fig.add_subplot(4, 3, 3*row_ctr + col_ctr) for col_ctr in range(1, 4)])
    axs = list(axs)
    axs.append(fig.add_subplot(4, 1, 4))

    # load old network
    ntwk_old = np.load(LOAD_FILE_NAME)[0]

    # demonstrate how one cannot use intrinsic plasticity to learn sequence that is made of disjoint paths
    path = list(ntwk_old.node_0_path_tree[0][:])
    path[2:] = ntwk_old.node_1_path_tree[0][2:]
    drives = np.zeros((TRIAL_LENGTH_TRIGGERED_REPLAY, ntwk_old.w.shape[0]), dtype=float)
    for ctr, node in enumerate(path):
        drives[ctr, node] = STRONG_DRIVE_AMPLITUDE
    drives[len(path), path[0]] = STRONG_DRIVE_AMPLITUDE

    for ctr, ax in enumerate(axs[0]):
        ntwk = deepcopy(ntwk_old)
        ntwk.store_voltages = True

        for drive in drives:
            ntwk.step(drive)

        spikes = np.array(ntwk.rs_history)

        fancy_raster.by_row_circles(ax, spikes, drives)

        ax.set_xlim(-1, len(drives))
        ax.set_ylim(-1, 20)
        ax.set_xlabel('time step')
        ax.set_ylabel('active \n ensemble')
        ax.set_title('Strongly driving nonexisting path (trial {})'.format(ctr + 1))

    w = ntwk_old.w.copy()

    # add weak connection to element 2 of node_1 path tree from element 1 of node_0 path tree
    w[ntwk_old.node_1_path_tree[0][2], ntwk_old.node_0_path_tree[0][1]] = W_WEAK

    # make new base network
    ntwk_base = network.RecurrentSoftMaxLingeringModel(
        w, GAIN, REFRACTORY_STRENGTH, LINGERING_INPUT_VALUE, LINGERING_INPUT_TIMESCALE
    )
    ntwk_base.node_0 = ntwk_old.node_0
    ntwk_base.node_0 = ntwk_old.node_1
    ntwk_base.node_0_path_tree = ntwk_old.node_0_path_tree
    ntwk_base.node_1_path_tree = ntwk_old.node_1_path_tree

    # demonstrate how weak connections allow linking of paths into short term memory
    path = list(ntwk_base.node_0_path_tree[0][:])
    path[2:] = ntwk_base.node_1_path_tree[0][2:]
    drives = np.zeros((TRIAL_LENGTH_TRIGGERED_REPLAY, ntwk_base.w.shape[0]), dtype=float)
    for ctr, node in enumerate(path):
        drives[ctr, node] = STRONG_DRIVE_AMPLITUDE
    drives[len(path), path[0]] = STRONG_DRIVE_AMPLITUDE

    for ctr, ax in enumerate(axs[1]):
        ntwk = deepcopy(ntwk_base)
        ntwk.store_voltages = True

        for drive in drives:
            ntwk.step(drive)

        spikes = np.array(ntwk.rs_history)

        fancy_raster.by_row_circles(ax, spikes, drives)

        ax.set_xlim(-1, len(drives))
        ax.set_ylim(-1, 20)
        ax.set_xlabel('time step')
        ax.set_ylabel('active \n ensemble')
        ax.set_title('Activity from forced initial condition after \n driving path with weak connection (trial {})'.format(ctr + 1))

    # demonstrate how weak connections do not substantially affect path probabilities
    path = list(ntwk_base.node_0_path_tree[0][:])
    path[2:] = ntwk_base.node_1_path_tree[0][2:]
    drives = np.zeros((TRIAL_LENGTH_TRIGGERED_REPLAY, ntwk_old.w.shape[0]), dtype=float)
    drives[0, path[0]] = STRONG_DRIVE_AMPLITUDE

    for ctr, ax in enumerate(axs[2]):
        ntwk = deepcopy(ntwk_base)
        ntwk.store_voltages = True

        for drive in drives:
            ntwk.step(drive)

        spikes = np.array(ntwk.rs_history)

        fancy_raster.by_row_circles(ax, spikes, drives)

        ax.set_xlim(-1, len(drives))
        ax.set_ylim(-1, 20)
        ax.set_xlabel('time step')
        ax.set_ylabel('active ensemble')
        ax.set_title('Free activity after only forcing \n initial condition (trial {})'.format(ctr + 1))

    path = list(ntwk_base.node_0_path_tree[0][:])
    path[2:] = ntwk_base.node_1_path_tree[0][2:]
    drives = np.zeros((RUN_LENGTH, ntwk_base.w.shape[0]), dtype=float)
    for ctr, node in enumerate(path):
        drives[ctr, node] = STRONG_DRIVE_AMPLITUDE

    ntwk = deepcopy(ntwk_base)
    ntwk.store_voltages = True
    for drive in drives:
        ntwk.step(drive)

    spikes = np.array(ntwk.rs_history)

    fancy_raster.by_row_circles(axs[3], spikes, drives)

    axs[3].set_xlim(-1, len(drives))
    axs[3].set_ylim(-1, 40)
    axs[3].set_xlabel('time step')
    axs[3].set_ylabel('active \n ensemble')
    axs[3].set_title('Free activity after driving path with weak connection')

    for ax_row in axs[:-1]:
        for ax in ax_row:
            axis_tools.set_fontsize(ax, FONT_SIZE)
    axis_tools.set_fontsize(axs[-1], FONT_SIZE)

    # now demonstrate how pattern-matching computation changes with respect to short-term memory
    fig, axs = plt.subplots(1, 2, figsize=FIG_SIZE_1, tight_layout=True)

    path = list(ntwk_base.node_0_path_tree[1][:])
    path[0] = 22
    path[2] = 17
    path[3] = ntwk_base.node_1_path_tree[0][3]

    drives_new = np.zeros((len(path), ntwk_base.w.shape[0]), dtype=float)
    drives_new[0, path[0]] = STRONG_DRIVE_AMPLITUDE
    for ctr, node in enumerate(path[1:]):
        drives_new[ctr + 1, node] = WEAK_DRIVE_AMPLITUDE

    # drive a network with just the new drive to see how it completes the pattern
    ntwk = deepcopy(ntwk_base)
    ntwk.store_voltages = True
    for drive in drives_new:
        ntwk.step(drive)

    spikes = np.array(ntwk.rs_history)

    fancy_raster.by_row_circles(axs[0], spikes, drives_new)

    axs[0].set_xlim(-1, 8)
    axs[0].set_ylim(-1, ntwk_base.w.shape[0])
    axs[0].set_xlabel('time step')
    axs[0].set_ylabel('active ensemble')
    axs[0].set_title('Weakly driving nonexistent path')

    drives = np.concatenate([drives[:4, :], drives_new])

    ntwk = deepcopy(ntwk_base)
    ntwk.store_voltages = True
    for drive in drives:
        ntwk.step(drive)

    spikes = np.array(ntwk.rs_history)

    fancy_raster.by_row_circles(axs[1], spikes, drives)

    axs[1].set_xlim(-1, 8)
    axs[1].set_ylim(-1, ntwk_base.w.shape[0])
    axs[1].set_xlabel('time step')
    axs[1].set_ylabel('active ensemble')
    axs[1].set_title('Weakly driving nonexistent path after \n strongly driving path with weak connection')

    for ax in axs:
        axis_tools.set_fontsize(ax, FONT_SIZE)
def basic_replay_stats(CONFIG):
    """
    Plot some statistics of this network's behavior.
    """

    SEED = CONFIG['SEED']

    LOAD_FILE_NAME = CONFIG['LOAD_FILE_NAME']

    GAIN_HIGH = CONFIG['GAIN_HIGH']
    GAIN_LOW = CONFIG['GAIN_LOW']

    LINGERING_INPUT_VALUE = CONFIG['LINGERING_INPUT_VALUE']
    LINGERING_INPUT_TIMESCALE = CONFIG['LINGERING_INPUT_TIMESCALE']

    STRONG_DRIVE_AMPLITUDE = CONFIG['STRONG_DRIVE_AMPLITUDE']

    T_SPONTANEOUS = CONFIG['T_SPONTANEOUS']
    SPONTANEOUS_REPEATS = CONFIG['SPONTANEOUS_REPEATS']

    FIG_SIZE = CONFIG['FIG_SIZE']
    FONT_SIZE = CONFIG['FONT_SIZE']

    np.random.seed(SEED)

    fig, axs = plt.subplots(1, 3, figsize=FIG_SIZE, tight_layout=True)

    ntwk_base = np.load(LOAD_FILE_NAME)[0]
    n_nodes = ntwk_base.w.shape[0]

    # show that adding in lingering activity increases probability of replay
    driven_path = ntwk_base.node_0_path_tree[0]

    p_replay_no_lingering = 1
    for node_prev, node_next in zip(driven_path[:-1], driven_path[1:]):
        intrinsic = ntwk_base.w[:, node_prev]
        refractory = np.zeros((n_nodes,), dtype=float)
        refractory[node_prev] = ntwk_base.refractory_strength
        inputs = intrinsic + refractory
        prob = np.exp(ntwk_base.gain * inputs)
        prob /= prob.sum()
        p_replay_no_lingering *= prob[node_next]

    lingering_inputs = np.zeros((n_nodes,), dtype=float)
    lingering_inputs[np.array(driven_path)] = LINGERING_INPUT_VALUE

    p_replay_with_lingering = 1
    for node_prev, node_next in zip(driven_path[:-1], driven_path[1:]):
        intrinsic = ntwk_base.w[:, node_prev]
        refractory = np.zeros((n_nodes,), dtype=float)
        refractory[node_prev] = ntwk_base.refractory_strength
        inputs = intrinsic + refractory + lingering_inputs
        prob = np.exp(ntwk_base.gain * inputs)
        prob /= prob.sum()
        p_replay_with_lingering *= prob[node_next]

    axs[0].bar([0, 1], [p_replay_no_lingering, p_replay_with_lingering], align='center')
    axs[0].set_xticks([0, 1])
    axs[0].set_xticklabels(['Without \n nonassociative \n priming', 'With \n nonassociative \n priming'])
    axs[0].set_ylabel('sequence replay probability')

    for ax, gain in zip(axs[1:], [GAIN_LOW, GAIN_HIGH]):

        # show expected number of spontaneous replays with and without nonassociative priming
        ntwk_no_nap = deepcopy(ntwk_base)
        ntwk_no_nap.gain = gain

        ntwk_with_nap = deepcopy(ntwk_base)
        ntwk_with_nap.gain = gain
        ntwk_with_nap.lingering_input_value = LINGERING_INPUT_VALUE
        ntwk_with_nap.lingering_input_timescale = LINGERING_INPUT_TIMESCALE

        drives = np.zeros((T_SPONTANEOUS, n_nodes), dtype=float)
        for t, node in enumerate(driven_path):
            drives[t, node] = STRONG_DRIVE_AMPLITUDE

        past_occurrences_of_driven_seq_no_nap = []
        for _ in range(SPONTANEOUS_REPEATS):
            ntwk = deepcopy(ntwk_no_nap)
            ntwk.store_voltages = True

            for drive in drives:
                ntwk.step(drive)

            activation_seq = np.array(ntwk.rs_history).nonzero()[1]

            past_occurrences_of_driven_seq_no_nap.append(
                metrics.get_number_of_past_occurrences_of_specific_sequence(activation_seq, driven_path)
            )

        past_occurrences_of_driven_seq_no_nap = np.array(past_occurrences_of_driven_seq_no_nap)
        past_occurrences_of_driven_seq_no_nap_mean = np.mean(past_occurrences_of_driven_seq_no_nap, axis=0)
        past_occurrences_of_driven_seq_no_nap_sem = stats.sem(past_occurrences_of_driven_seq_no_nap, axis=0)

        past_occurrences_of_driven_seq_with_nap = []
        for _ in range(SPONTANEOUS_REPEATS):
            ntwk = deepcopy(ntwk_with_nap)
            ntwk.store_voltages = True

            for drive in drives:
                ntwk.step(drive)

            activation_seq = np.array(ntwk.rs_history).nonzero()[1]

            past_occurrences_of_driven_seq_with_nap.append(
                metrics.get_number_of_past_occurrences_of_specific_sequence(activation_seq, driven_path)
            )

        past_occurrences_of_driven_seq_with_nap = np.array(past_occurrences_of_driven_seq_with_nap)
        past_occurrences_of_driven_seq_with_nap_mean = np.mean(past_occurrences_of_driven_seq_with_nap, axis=0)
        past_occurrences_of_driven_seq_with_nap_sem = stats.sem(past_occurrences_of_driven_seq_with_nap, axis=0)

        ts = np.arange(T_SPONTANEOUS)
        ax.plot(ts, past_occurrences_of_driven_seq_no_nap_mean, color='b', lw=2)
        ax.fill_between(
            ts,
            past_occurrences_of_driven_seq_no_nap_mean - past_occurrences_of_driven_seq_no_nap_sem,
            past_occurrences_of_driven_seq_no_nap_mean + past_occurrences_of_driven_seq_no_nap_sem,
            color='b',
            alpha=0.3,
        )

        ax.plot(ts, past_occurrences_of_driven_seq_with_nap_mean, color='g', lw=2)
        ax.fill_between(
            ts,
            past_occurrences_of_driven_seq_with_nap_mean - past_occurrences_of_driven_seq_with_nap_sem,
            past_occurrences_of_driven_seq_with_nap_mean + past_occurrences_of_driven_seq_with_nap_sem,
            color='g',
            alpha=0.3,
        )

        ax.set_xlabel('time step')
        ax.set_ylabel('past occurrences')
        ax.set_title('gain = {}'.format(gain))
        ax.legend(['Without nonasso-\n ciative priming', 'With nonasso- \n ciative priming'], loc='best')

    for ax in axs:
        axis_tools.set_fontsize(ax, FONT_SIZE)
def basic_replay_ex(CONFIG):
    """
    Run a simulation demonstrating the basic capability of a network with nonassociative priming to
    demonstrated replay, both triggered and spontaneous.
    """

    SEED = CONFIG['SEED']

    LOAD_FILE_NAME = CONFIG['LOAD_FILE_NAME']

    GAIN_HIGH = CONFIG['GAIN_HIGH']
    GAIN_LOW = CONFIG['GAIN_LOW']

    LINGERING_INPUT_VALUE = CONFIG['LINGERING_INPUT_VALUE']
    LINGERING_INPUT_TIMESCALE = CONFIG['LINGERING_INPUT_TIMESCALE']

    STRONG_DRIVE_AMPLITUDE = CONFIG['STRONG_DRIVE_AMPLITUDE']
    WEAK_DRIVE_AMPLITUDE = CONFIG['WEAK_DRIVE_AMPLITUDE']

    TRIAL_LENGTH_TRIGGERED_REPLAY = CONFIG['TRIAL_LENGTH_TRIGGERED_REPLAY']
    RUN_LENGTH = CONFIG['RUN_LENGTH']

    FIG_SIZE = CONFIG['FIG_SIZE']
    FONT_SIZE = CONFIG['FONT_SIZE']

    np.random.seed(SEED)

    ntwk_base = np.load(LOAD_FILE_NAME)[0]
    ntwk_base.lingering_input_value = LINGERING_INPUT_VALUE
    ntwk_base.lingering_input_timescale = LINGERING_INPUT_TIMESCALE

    fig = plt.figure(figsize=FIG_SIZE, tight_layout=True)
    axs = []
    axs.append(fig.add_subplot(6, 2, 1))
    axs.append(fig.add_subplot(6, 2, 2))
    axs.append(fig.add_subplot(6, 2, 3))
    axs.append(fig.add_subplot(6, 2, 4))
    axs.append(fig.add_subplot(6, 1, 3))
    axs.append(fig.add_subplot(6, 1, 4))
    axs.append(fig.add_subplot(6, 1, 5))
    axs.append(fig.add_subplot(6, 1, 6))

    # play sequences aligned to the network's intrinsic path structure
    path_00 = ntwk_base.node_0_path_tree[0]
    path_10 = ntwk_base.node_1_path_tree[0]

    # drive network for first trial: path_00
    drives = np.zeros((TRIAL_LENGTH_TRIGGERED_REPLAY, ntwk_base.w.shape[1]), dtype=float)
    drives[0, path_00[0]] = STRONG_DRIVE_AMPLITUDE
    for t_ctr, node in enumerate(path_00[1:]):
        drives[t_ctr + 1, node] = WEAK_DRIVE_AMPLITUDE
    drives[len(path_00), path_00[0]] = STRONG_DRIVE_AMPLITUDE

    ntwk = deepcopy(ntwk_base)
    ntwk.gain = GAIN_HIGH
    ntwk.store_voltages = True

    for drive in drives:
        ntwk.step(drive)

    spikes = np.array(ntwk.rs_history)

    fancy_raster.by_row_circles(axs[0], spikes, drives)

    axs[0].set_xlim(-1, len(drives))
    axs[0].set_ylim(-1, 20)
    axs[0].set_xlabel('time step')
    axs[0].set_ylabel('active ensemble')
    axs[0].set_title('Aligning external drive with \n strongly connected paths')

    # drive network for first trial: path_10
    drives = np.zeros((TRIAL_LENGTH_TRIGGERED_REPLAY, ntwk_base.w.shape[1]), dtype=float)
    drives[0, path_10[0]] = STRONG_DRIVE_AMPLITUDE
    for t_ctr, node in enumerate(path_10[1:]):
        drives[t_ctr + 1, node] = WEAK_DRIVE_AMPLITUDE
    drives[len(path_10), path_10[0]] = STRONG_DRIVE_AMPLITUDE

    ntwk = deepcopy(ntwk_base)
    ntwk.gain = GAIN_HIGH
    ntwk.store_voltages = True

    for drive in drives:
        ntwk.step(drive)

    spikes = np.array(ntwk.rs_history)

    fancy_raster.by_row_circles(axs[1], spikes, drives)

    axs[1].set_xlim(-1, len(drives))
    axs[1].set_ylim(-1, 20)
    axs[1].set_xlabel('time step')
    axs[1].set_ylabel('active ensemble')
    axs[1].set_title('Aligning external drive with \n strongly connected paths')

    # drive network for third trial: all path_00 except for element 2
    path = list(path_00[:])
    path[2] = path_10[2]
    drives = np.zeros((TRIAL_LENGTH_TRIGGERED_REPLAY, ntwk_base.w.shape[1]), dtype=float)
    drives[0, path[0]] = STRONG_DRIVE_AMPLITUDE
    for t_ctr, node in enumerate(path[1:]):
        drives[t_ctr + 1, node] = WEAK_DRIVE_AMPLITUDE
    drives[len(path), path[0]] = STRONG_DRIVE_AMPLITUDE

    ntwk = deepcopy(ntwk_base)
    ntwk.gain = GAIN_HIGH
    ntwk.store_voltages = True

    for drive in drives:
        ntwk.step(drive)

    spikes = np.array(ntwk.rs_history)

    fancy_raster.by_row_circles(axs[2], spikes, drives)

    axs[2].set_xlim(-1, len(drives))
    axs[2].set_ylim(-1, 20)
    axs[2].set_xlabel('time step')
    axs[2].set_ylabel('active ensemble')
    axs[2].set_title('Aligning external drive with \n nonexisting path')

    # drive network for fourth trial: all path_10 except for element 2
    path = list(path_10[:])
    path[2] = path_00[2]
    drives = np.zeros((TRIAL_LENGTH_TRIGGERED_REPLAY, ntwk_base.w.shape[1]), dtype=float)
    drives[0, path[0]] = STRONG_DRIVE_AMPLITUDE
    for t_ctr, node in enumerate(path[1:]):
        drives[t_ctr + 1, node] = WEAK_DRIVE_AMPLITUDE
    drives[len(path), path[0]] = STRONG_DRIVE_AMPLITUDE

    ntwk = deepcopy(ntwk_base)
    ntwk.gain = GAIN_HIGH
    ntwk.store_voltages = True

    for drive in drives:
        ntwk.step(drive)

    spikes = np.array(ntwk.rs_history)

    fancy_raster.by_row_circles(axs[3], spikes, drives)

    axs[3].set_xlim(-1, len(drives))
    axs[3].set_ylim(-1, 20)
    axs[3].set_xlabel('time step')
    axs[3].set_ylabel('active ensemble')
    axs[3].set_title('Aligning external drive with \n nonexisting path')

    # play sequence and then let network run spontaneously for a while
    drives = np.zeros((RUN_LENGTH, ntwk_base.w.shape[1]), dtype=float)
    for t_ctr, node in enumerate(path_00):
        drives[t_ctr, node] = STRONG_DRIVE_AMPLITUDE

    ntwk = deepcopy(ntwk_base)
    ntwk.gain = GAIN_HIGH
    ntwk.store_voltages = True

    for drive in drives:
        ntwk.step(drive)

    spikes = np.array(ntwk.rs_history)

    fancy_raster.by_row_circles(axs[4], spikes, drives)

    axs[4].set_xlim(-1, len(drives))
    axs[4].set_ylim(-1, ntwk_base.w.shape[1])
    axs[4].set_xlabel('time step')
    axs[4].set_ylabel('active ensemble')
    axs[4].set_title('Letting network run freely after driving strongly connected path (high gain)')

    # play sequence and then let network run spontaneously for a while, now with lower gain
    drives = np.zeros((RUN_LENGTH, ntwk_base.w.shape[1]), dtype=float)
    for t_ctr, node in enumerate(path_00):
        drives[t_ctr, node] = STRONG_DRIVE_AMPLITUDE

    ntwk = deepcopy(ntwk_base)
    ntwk.gain = GAIN_LOW
    ntwk.store_voltages = True

    for drive in drives:
        ntwk.step(drive)

    spikes = np.array(ntwk.rs_history)

    fancy_raster.by_row_circles(axs[5], spikes, drives)

    axs[5].set_xlim(-1, len(drives))
    axs[5].set_ylim(-1, ntwk_base.w.shape[1])
    axs[5].set_xlabel('time step')
    axs[5].set_ylabel('active ensemble')
    axs[5].set_title('Letting network run freely after driving strongly connected path (low gain)')

    # let network run spontaneously for a while with no initial drive
    ntwk = deepcopy(ntwk_base)
    ntwk.gain = GAIN_HIGH
    ntwk.store_voltages = True

    for _ in range(RUN_LENGTH):
        ntwk.step()

    spikes = np.array(ntwk.rs_history)

    fancy_raster.by_row_circles(axs[6], spikes, drives=None)

    axs[6].set_xlim(-1, len(drives))
    axs[6].set_ylim(-1, ntwk_base.w.shape[1])
    axs[6].set_xlabel('time step')
    axs[6].set_ylabel('active ensemble')
    axs[6].set_title('Letting network run freely with no drive (high gain)')

    # let network run spontaneously for a while with no initial drive, now with lower gain
    ntwk = deepcopy(ntwk_base)
    ntwk.gain = GAIN_LOW
    ntwk.store_voltages = True

    for _ in range(RUN_LENGTH):
        ntwk.step()

    spikes = np.array(ntwk.rs_history)

    fancy_raster.by_row_circles(axs[7], spikes, drives=None)

    axs[7].set_xlim(-1, len(drives))
    axs[7].set_ylim(-1, ntwk_base.w.shape[1])
    axs[7].set_xlabel('time step')
    axs[7].set_ylabel('active ensemble')
    axs[7].set_title('Letting network run freely with no drive (low gain)')

    for ax in axs:
        axis_tools.set_fontsize(ax, FONT_SIZE)
def main(config):
    # get params from config
    SEED = config['SEED']
    DURATION = config['DURATION']
    
    TAU = config['TAU']
    V_REST = config['V_REST']
    THRESHOLD = config['THRESHOLD']
    STEEPNESS = config['STEEPNESS']
    NOISE_LEVEL = config['NOISE_LEVEL']
    
    W_FS = config['W_FS']
    W_FF = config['W_FF']
    W_FI = config['W_FI']
    W_XFI = config['W_XFI']
    W_IF = config['W_IF']
    
    SWITCH_DRIVE = config['SWITCH_DRIVE']
    
    FONT_SIZE = config['FONT_SIZE']
    COLOR_CYCLE = config['COLOR_CYCLE']
    
    ## run simulation with single WTA unit
    # setup network parameters; order: (switch, fast, fast_inh)
    w_single = np.array([
        [0, 0, 0],  # to switch
        [W_FS, W_FF, W_FI],  # to fast
        [0, W_IF, 0],  # to fast_inh
    ])
    nodes_single = 3 * [{'tau': TAU, 'v_rest': V_REST, 'threshold': THRESHOLD, 'steepness': STEEPNESS}]
    
    # build network
    ntwk = network.RateBasedModel(nodes_single, w_single)
    ntwk.noise_level = NOISE_LEVEL
    ntwk.store_voltages = True
    
    # set network drive
    drive = np.array([SWITCH_DRIVE, 0, 0])
    
    # run network
    np.random.seed(SEED)
    ntwk.vs = np.array([V_REST, V_REST, V_REST])
    for t in range(DURATION):
        ntwk.step(drive)
        
    # make figure
    fig, axs = plt.subplots(2, 1, figsize=(15, 5), sharex=True, tight_layout=True)
    for ax in axs:
        ax.set_color_cycle(COLOR_CYCLE)
        
    axs[0].plot(ntwk.vs_history, lw=2)
    axs[1].plot(ntwk.rs_history, lw=2)
    
    axs[1].set_ylim(-.1, 1.1)
    axs[1].set_yticks(np.linspace(0, 1, 5, endpoint=True))
    axs[0].set_ylabel('Voltage')
    axs[1].set_xlabel('t')
    axs[1].set_ylabel('Firing rate')
    
    axs[0].set_title('Single WTA Unit')
    
    for ax in axs:
        axis_tools.set_fontsize(ax, FONT_SIZE)
        
    ## run simulation with three non-interacting WTA units
    # setup network parameters; order: (switch, fast, fast_inh, fast, fast_inh, fast, fast_inh)
    w_multi = np.array([
        [0, 0, 0, 0, 0, 0, 0],  # to switch
        [W_FS, W_FF, W_FI, 0, 0, 0, 0],  # to fast1
        [0, W_IF, 0, 0, 0, 0, 0],  # to fast_inh1
        [W_FS, 0, 0, W_FF, W_FI, 0, 0],  # to fast2
        [0, 0, 0, W_IF, 0, 0, 0],  # to fast_inh2
        [W_FS, 0, 0, 0, 0, W_FF, W_FI],  # to fast3
        [0, 0, 0, 0, 0, W_IF, 0],  # to fast_inh3
    ])
    nodes_multi = 7 * [{'tau': TAU, 'v_rest': V_REST, 'threshold': THRESHOLD, 'steepness': STEEPNESS}]
    
    # build network
    ntwk = network.RateBasedModel(nodes_multi, w_multi)
    ntwk.noise_level = NOISE_LEVEL
    ntwk.store_voltages = True
    
    # set network drive
    drive = np.array([SWITCH_DRIVE, 0, 0, 0, 0, 0, 0])
    
    # run network
    np.random.seed(SEED)
    ntwk.vs = np.array([V_REST, V_REST, V_REST, V_REST, V_REST, V_REST, V_REST])
    for t in range(DURATION):
        ntwk.step(drive)

    # make figure
    fig, axs = plt.subplots(2, 1, figsize=(15, 5), sharex=True, tight_layout=True)
    for ax in axs:
        ax.set_color_cycle(COLOR_CYCLE)
        
    axs[0].plot(np.array(ntwk.vs_history)[:, [1, 3, 5]], lw=2)
    axs[1].plot(np.array(ntwk.rs_history)[:, [1, 3, 5]], lw=2)
    
    axs[1].set_ylim(-.1, 1.1)
    axs[1].set_yticks(np.linspace(0, 1, 5, endpoint=True))
    axs[0].set_ylabel('Voltage')
    axs[1].set_xlabel('t')
    axs[1].set_ylabel('Firing rate')
    
    axs[0].set_title('Three Independent WTA Units')
    
    for ax in axs:
        axis_tools.set_fontsize(ax, FONT_SIZE)
        
        
    ## run simulation with three interacting WTA units
    # setup network parameters; order: (switch, fast1, fast_inh1, fast2, fast_inh2, fast3, fast_inh3)
    w_multi = np.array([
        [0, 0, 0, 0, 0, 0, 0],  # to switch
        [W_FS, W_FF, W_FI, 0, W_XFI, 0, W_XFI],  # to fast1
        [0, W_IF, 0, 0, 0, 0, 0],  # to fast_inh1
        [W_FS, 0, W_XFI, W_FF, W_FI, 0, W_XFI],  # to fast2
        [0, 0, 0, W_IF, 0, 0, 0],  # to fast_inh2
        [W_FS, 0, W_XFI, 0, W_XFI, W_FF, W_FI],  # to fast3
        [0, 0, 0, 0, 0, W_IF, 0],  # to fast_inh3
    ])
    nodes_multi = 7 * [{'tau': TAU, 'v_rest': V_REST, 'threshold': THRESHOLD, 'steepness': STEEPNESS}]
    
    # build network
    ntwk = network.RateBasedModel(nodes_multi, w_multi)
    ntwk.noise_level = NOISE_LEVEL
    ntwk.store_voltages = True
    
    # set network drive
    drive = np.array([SWITCH_DRIVE, 0, 0, 0, 0, 0, 0])
    
    # run network
    np.random.seed(SEED)
    ntwk.vs = np.array([V_REST, V_REST, V_REST, V_REST, V_REST, V_REST, V_REST])
    for t in range(DURATION):
        ntwk.step(drive)

    # make figure
    fig, axs = plt.subplots(2, 1, figsize=(15, 5), sharex=True, tight_layout=True)
    for ax in axs:
        ax.set_color_cycle(COLOR_CYCLE)
        
    axs[0].plot(np.array(ntwk.vs_history)[:, [1, 3, 5]], lw=2)
    axs[1].plot(np.array(ntwk.rs_history)[:, [1, 3, 5]], lw=2)
    
    axs[1].set_ylim(-.1, 1.1)
    axs[1].set_yticks(np.linspace(0, 1, 5, endpoint=True))
    axs[0].set_ylabel('Voltage')
    axs[1].set_xlabel('t')
    axs[1].set_ylabel('Firing rate')
    
    axs[0].set_title('Three Interacting WTA Units')
    
    for ax in axs:
        axis_tools.set_fontsize(ax, FONT_SIZE)
    
    
    ## run simulation with three fast units all of whose interactions occur through one inhibitory unit
    # setup network parameters; order: (switch, inh, fast1, fast2, fast3)
    w_multi = np.array([
        [0, 0, 0, 0, 0],  # to switch
        [0, 0, W_IF, W_IF, W_IF],  # to inh
        [W_FS, W_FI, W_FF, 0, 0],  # to fast1
        [W_FS, W_FI, 0, W_FF, 0],  # to fast2
        [W_FS, W_FI, 0, 0, W_FF],  # to fast3
    ])
    nodes_multi = 5 * [{'tau': TAU, 'v_rest': V_REST, 'threshold': THRESHOLD, 'steepness': STEEPNESS}]
    
    # build network
    ntwk = network.RateBasedModel(nodes_multi, w_multi)
    ntwk.noise_level = NOISE_LEVEL
    ntwk.store_voltages = True
    
    # set network drive
    drive = np.array([SWITCH_DRIVE, 0, 0, 0, 0])
    
    # run network
    np.random.seed(SEED)
    ntwk.vs = np.array([V_REST, V_REST, V_REST, V_REST, V_REST])
    for t in range(DURATION):
        ntwk.step(drive)

    # make figure
    fig, axs = plt.subplots(2, 1, figsize=(15, 5), sharex=True, tight_layout=True)
    for ax in axs:
        ax.set_color_cycle(COLOR_CYCLE)
        
    axs[0].plot(np.array(ntwk.vs_history)[:, 2:], lw=2)
    axs[1].plot(np.array(ntwk.rs_history)[:, 2:], lw=2)
    
    axs[1].set_ylim(-.1, 1.1)
    axs[1].set_yticks(np.linspace(0, 1, 5, endpoint=True))
    axs[0].set_ylabel('Voltage')
    axs[1].set_xlabel('t')
    axs[1].set_ylabel('Firing rate')
    
    axs[0].set_title('Three Exc Units Sharing One Inh Unit')
    
    for ax in axs:
        axis_tools.set_fontsize(ax, FONT_SIZE)
    
    ## run simulation with many fast units all of whose interactions occur through one inhibitory unit
    # setup network parameters; order: (switch, inh, fast1, fast2, fast3)
    w_multi = np.array([
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],  # to switch
        [0, 0, W_IF, W_IF, W_IF, W_IF, W_IF, W_IF, W_IF, W_IF],  # to inh
        [W_FS, W_FI, W_FF, 0, 0, 0, 0, 0, 0, 0],  # to fast1
        [W_FS, W_FI, 0, W_FF, 0, 0, 0, 0, 0, 0],  # to fast2
        [W_FS, W_FI, 0, 0, W_FF, 0, 0, 0, 0, 0],  # to fast3
        [W_FS, W_FI, 0, 0, 0, W_FF, 0, 0, 0, 0],  # to fast3
        [W_FS, W_FI, 0, 0, 0, 0, W_FF, 0, 0, 0],  # to fast3
        [W_FS, W_FI, 0, 0, 0, 0, 0, W_FF, 0, 0],  # to fast3
        [W_FS, W_FI, 0, 0, 0, 0, 0, 0, W_FF, 0],  # to fast3
        [W_FS, W_FI, 0, 0, 0, 0, 0, 0, 0, W_FF],  # to fast3
    ])
    nodes_multi = 10 * [{'tau': TAU, 'v_rest': V_REST, 'threshold': THRESHOLD, 'steepness': STEEPNESS}]
    
    # build network
    ntwk = network.RateBasedModel(nodes_multi, w_multi)
    ntwk.noise_level = NOISE_LEVEL
    ntwk.store_voltages = True
    
    # set network drive
    drive = np.array([SWITCH_DRIVE, 0, 0, 0, 0, 0, 0, 0, 0, 0])
    
    # run network
    np.random.seed(SEED)
    ntwk.vs = np.array([V_REST, V_REST, V_REST, V_REST, V_REST, V_REST, V_REST, V_REST, V_REST, V_REST])
    for t in range(DURATION):
        ntwk.step(drive)

    # make figure
    fig, axs = plt.subplots(2, 1, figsize=(15, 5), sharex=True, tight_layout=True)
    for ax in axs:
        ax.set_color_cycle(COLOR_CYCLE)
        
    axs[0].plot(np.array(ntwk.vs_history)[:, 2:], lw=2)
    axs[1].plot(np.array(ntwk.rs_history)[:, 2:], lw=2)
    
    axs[1].set_ylim(-.1, 1.1)
    axs[1].set_yticks(np.linspace(0, 1, 5, endpoint=True))
    axs[0].set_ylabel('Voltage')
    axs[1].set_xlabel('t')
    axs[1].set_ylabel('Firing rate')
    
    axs[0].set_title('Eight Exc Units Sharing One Inh Unit')
    
    for ax in axs:
        axis_tools.set_fontsize(ax, FONT_SIZE)