def runSimulations(knockouts=True):
    print "starting Simulations"
    data = {}
    targets = [
        'CPK213', 'HATPase', 'NOGC1', 'NAD', 'ABA', 'Microtubule', 'Depolar',
        'InsP3', 'SphK12', 'ABH1', 'InsP6', 'CIS', 'AnionEM', 'GAPC',
        'KEfflux', 'H2OEfflux', 'VPpase', 'PP2CA', 'MRP5', 'CPK23',
        'Vacidification', 'DAG', 'ROP11', 'NO', 'pHc', 'PIP21', 'PEPC',
        'NitrocGMP', 'HAB1', 'DAGK', 'PLDd', 'PC', 'CPK6', 'PA', 'PLDa', 'PLC',
        'PI3P5K', 'ROS', 'AtRAC1', 'OST1', 'cGMP', 'Ca2ATPase', 'GCR1', 'RCN1',
        'PIP2', 'QUAC1', 'S1P', 'Malate', 'KOUT', 'NADPH', 'MAPK912', 'KEV',
        'SCAB1', 'CaIM', 'TCTP', 'VATPase', 'GPA1', 'PtdIns35P2', 'GTP',
        'GEF1410', 'ARP23', 'PtdInsP4', 'Sph', 'ABI1', 'NIA12', 'ADPRc',
        'RCARs', 'PtdInsP3', 'Nitrite', 'ABI2', 'SPP1', 'RBOH', 'Actin',
        'ERA1', 'NtSyp121', 'Ca2c', 'GHR1', 'cADPR', 'SLAH3', 'SLAC1'
    ]
    for target in targets:
        if knockouts is True:
            mtext = boolean2.modify_states(text=text, turnoff=target)
            fname = 'knockouts.json'
        else:
            mtext = boolean2.modify_states(text=text, turnon=target)
            fname = 'overexpression.json'
        model = boolean2.Model(mode='async', text=mtext)
        coll = util.Collector()
        for i in xrange(repeat):
            model.initialize(missing=util.randbool)
            model.iterate(steps=steps)
            coll.collect(states=model.states, nodes='Closure')
        data[target] = {'Timesteps': coll.get_averages(normalize=True)}
        data[target]['Closure AUC'] = sum(data[target]['Timesteps']['Closure'])
    with open(fname, 'w') as fp:
        json.dump(data, fp)
Ejemplo n.º 2
0
def runSimulations(knockouts=True):
    print "starting Simulations"
    data = {}
    #use the following set of targets if running the simulation for the reduced model
    #targets = ['NOGC1', 'PLDdel', 'InsP3', 'nitrocGMP', 'CIS', 'Actin', 'AnionEM', 'K_efflux', 'PP2CA', 'H2O_Efflux', 'DAG', 'NO', 'pHc', 'PEPC', 'HAB1', 'PA',
    #'PLDa', 'PLC', 'PI3P5K', 'OST1', 'SLAH3', 'ABA', 'H_ATPase', 'Malate', 'KOUT', 'Depolarization', 'QUAC1', 'CaIM', 'TCTP', 'AtRAC1', 'SPHK12', 'Ca_ATPase',
    #'CPKa', 'CPKb', 'ABI2', 'Microtubule_Depolymerization', 'Ca', 'ABI1', 'NIA12', 'MPK', 'RCARs', 'VATPase', 'Vacuolar_Acidification', 'ROP11', 'RBOH', 'KEV',
    #'GHR1', 'SLAC1', 'WT']

    #use the following set of targets when running the simulation for the full model
    #targets = ['CPK321', 'H_ATPase', 'NOGC1', 'ABA', 'Microtubule_Depolymerization', 'Depolarization', 'InsP3', 'SPHK12', 'ABH1', 'InsP6', 'CIS', 'AnionEM',
    #'GAPC12', 'K_efflux', 'H2O_Efflux', 'VPPase', 'PP2CA', 'MRP5', 'CPK23', 'Vacuolar_Acidification', 'DAG', 'ROP11', 'NO', 'pHc', 'PIP', 'PEPC', 'nitrocGMP',
    #'HAB1', 'DAGK', 'PLDdel', 'PC', 'CPK6', 'PA', 'PLDa', 'PLC', 'PI3P5K', 'ROS', 'AtRAC1', 'OST1', 'cGMP', 'Ca2_ATPase', 'GCR1', 'RCN1', 'QUAC1', 'S1P',
    #'Malate', 'KOUT', 'NADPH', 'MPK912', 'KEV', 'SCAB1', 'CaIM', 'TCTP', 'VATPase', 'GPA1', 'PtdIns35P2', 'GTP', 'GEF', 'ARP_Complex', 'PtdInsP4', 'Sph',
    #'ABI1', 'NIA12', 'ADPRc', 'RCARs', 'PtdInsP3', 'Nitrite', 'ABI2', 'SPP1', 'RBOH', 'Actin_Reorganization', 'ERA1', 'NtSyp121', 'Ca2', 'GHR1', 'cADPR',
    #'SLAH3', 'SLAC1', 'WT', 'PtdIns45P2']

    for target in targets:
        print 'Processing target', target
        if knockouts is True:
            mtext = boolean2.modify_states(text=text, turnoff=target)
            fname = 'results_KO.json'
        else:
            mtext = boolean2.modify_states(text=text, turnon=target)
            fname = 'results_CA.json'
        model = boolean2.Model(mode='async', text=mtext)
        coll = util.Collector()
        for i in xrange(repeat):
            model.initialize(missing=util.randbool)
            model.iterate(steps=steps)
            coll.collect(states=model.states, nodes='Closure')
        data[target] = {'Timesteps': coll.get_averages(normalize=True)}
        data[target]['Closure AUC'] = sum(data[target]['Timesteps']['Closure'])
    with open(fname, 'w') as fp:
        json.dump(data, fp)
Ejemplo n.º 3
0
def run(text, nodes, repeat, steps):
    """
    Runs the simulation and collects the nodes into a collector, 
    a convenience class that can average the values that it collects.
    """
    coll = util.Collector()

    for i in xrange(repeat):
        engine = Model(mode='async', text=text)
        engine.RULE_GETVALUE = new_getvalue
        # minimalist initial conditions, missing nodes set to false
        engine.initialize(missing=util.false)
        engine.iterate(steps=steps)
        coll.collect(states=engine.states, nodes=nodes)

    print '- completed'
    avgs = coll.get_averages(normalize=True)
    return avgs
Ejemplo n.º 4
0
def run_mutations(text, repeat, steps):
    "Runs the asynchronous model with different mutations"

    # WT does not exist so it won't affect anything

    data = {}
    knockouts = 'WT S1P PA pHc ABI1 ROS'.split()
    for target in knockouts:
        print('- target %s' % target)
        mtext = boolean2.modify_states(text=text, turnoff=target)
        model = Model(mode='async', text=mtext)
        coll = util.Collector()
        for i in range(repeat):
            # unintialized nodes set to random
            model.initialize(missing=util.randbool)
            model.iterate(steps=steps)
            coll.collect(states=model.states, nodes=model.nodes)
        data[target] = coll.get_averages(normalize=True)

    return data
def all_ss_model_w_fix(str_file,
                       str_mode,
                       timecourse,
                       csv_out_txt,
                       geneon=[],
                       geneoff=[]):
    '''
    A function to generate a list of all possible starting states of a model. Note that this function is a
    modification of the all_ss_model() function and allows for specific nodes to be fixed on or off.
    str_file: as a string, write the name of the file containing the rules for the boolean model
    str_mode: enter the mode of model that is going to be used, ie sync or async
    numnodes: the number of nodes in the model
    :return:
    A list of dictionaries with all possible starting states of the network.
    '''
    Bool1 = file(str_file).read()

    # Generate a new version of the model that allows the genes to be fixed on or off by removing updating rules for
    # them. This will not override the initialized value however, and that must be updated to be assigned on or off
    # separately.
    on = geneon
    off = geneoff
    Bool2 = tokenizer.modify_states(Bool1, on, off)
    model = Model(text=Bool2, mode=str_mode)
    initializer = state.all_initial_states(model.nodes, limit=None)
    n = 0
    d = []

    # Utilized in the for loops for the loading bar
    load_status_divisor = len(model.nodes) - len(geneoff) - len(geneon)

    # The BooleanNet Data Collector. Here it is implemented to gather data on the states of the nodes in the model.
    coll = util.Collector()

    # Wiley Stoeber. 5/8/18. Create a modified version of the initializer that will pass over initial states that
    # contradict the gene set mode
    initializer_new = []

    if geneoff != [] or geneon != []:
        for data_init in initializer:
            data = data_init[0]
            for i in range(len(geneoff)):
                if data[str(geneoff[i])]:
                    initializer_new.append(data_init)
            for i in range(len(geneon)):
                if data[str(geneon[i])]:
                    initializer_new.append(data_init)

        for data, initfunc in initializer_new:
            # Fixes genes on or off (True or False) at their starting state.
            for i in range(len(geneoff)):
                data.update({str(geneoff[i]): False})
            for i in range(len(geneon)):
                data.update({str(geneon[i]): True})

            # Initialize the model with the given pre-computed initial conditions stored in the data variable.
            # for a given model with Z nodes, there are 2 to the power of Z starting states.
            model.initialize(defaults=data)
            model.iterate(steps=timecourse)
            e = model.detect_cycles()
            nodes = [
                'Apoptosis', 'Proliferation', 'Angiogenesis', 'Differentiation'
            ]

            d.append(list(model.detect_cycles()))
            d[n].append(model.fp())
            d[n].append(model.first)
            d[n].append(model.last)
            detect_states = 0 - e[1]
            coll.collect(states=model.states[detect_states:], nodes=nodes)

            # Converts collected data back to immutable tuple
            # d[n] = tuple(d[n])

            # Show data as it is generated. For debug purposes
            print d[n]
            # this print function is the status bar output.
            print((n) / float(pow(2, load_status_divisor))) * 100, "% done."
            n += 1
    else:
        for data, initfunc in initializer:
            # Fixes genes on or off (True or False) at their starting state.
            for i in range(len(geneoff)):
                data.update({str(geneoff[i]): False})
            for i in range(len(geneon)):
                data.update({str(geneon[i]): True})

            # Initialize the model with the given pre-computed initial conditions stored in the data variable.
            # for a given model with Z nodes, there are 2 to the power of Z starting states.
            model.initialize(defaults=data)
            model.iterate(steps=timecourse)
            e = model.detect_cycles()
            nodes = [
                'Apoptosis', 'Proliferation', 'Angiogenesis', 'Differentiation'
            ]

            d.append(list(model.detect_cycles()))
            d[n].append(model.fp())
            d[n].append(model.first)
            d[n].append(model.last)
            detect_states = 0 - e[1]
            coll.collect(states=model.states[detect_states:], nodes=nodes)

            # Converts collected data back to immutable tuple
            # d[n] = tuple(d[n])

            # Show data as it is generated. For debug purposes
            print d[n]
            # this print function is the status bar output.
            print((n) / float(pow(2, load_status_divisor))) * 100, "% done."
            n += 1

    # Add the avg on state of the nodes specified by the user. They will output to new columns in row 1 of the data

    avgs = coll.get_averages(normalize=True)
    Angiogenesis = avgs['Angiogenesis']
    Proliferation = avgs['Proliferation']
    Apoptosis = avgs['Apoptosis']
    Differentiation = avgs['Differentiation']

    # Append the average Data to the data set
    d[0].append(Apoptosis[0])
    d[0].append(Proliferation[0])
    d[0].append(Angiogenesis[0])
    d[0].append(Differentiation[0])

    # header names for pandas dataframe
    headers = ['Index', 'CycleLength', 'CycleFingerprint', 'FirstState', 'LastState',\
               'Avg_Apoptosis', 'Avg_Proliferation', 'Avg_Angiogenesis', 'Avg_Differentiation']

    # convert information to a pandas dataframe

    df = pd.DataFrame(d)
    df.columns = headers
    print

    # Export data to csv
    with open(csv_out_txt, 'wb') as out:
        csv_out = csv.writer(out)
        csv_out.writerow(headers)
        for row in d:
            csv_out.writerow(row)
    return df
Ejemplo n.º 6
0
    else:
        return 0

def hybrid_f(delta, vegfr, icd, jagged, notch):
    if delta == 1 and vegfr == 1 and icd == 1 and jagged == 1 and notch == 1:
        return 1
    else:
        return 0

def other(tip, hybrid, stalk):
    if tip == 0 and hybrid == 0 and stalk == 0:
        return 1
    else:
        return 0

coll = util.Collector()
flag = True
N = 10
sim = int(30)
t0 = 0
choices_list = ['cis_delta', 'cis_jagged', 'trans_delta', 'trans_jagged']
#while flag == True:

d_ss = []
j_ss = []
i_ss = []
n_ss = []
vr_ss = []
v_ss = []

d_ss_j = []
Ejemplo n.º 7
0
def BoolMod(timecourse, booldata, str_mode):
    coll = util.Collector()
    model = Model(text=booldata, mode=str_mode)
    initializer = state.all_initial_states(model.nodes, limit=None)

    # the data is the inital data, the func is the initializer
    n = 0
    for data, initfunc in initializer:
        # shows the initial values
        print((n) / float(32768)) * 100, "% done."
        n = n + 1
        model.initialize(missing=initfunc)
        model.iterate(steps=timecourse)
        # takes all nodes
        nodes = model.nodes
        coll.collect(states=model.states, nodes=nodes)
    # --------------- Detect Cycles ------------------#
    print model.report_cycles()
    # Return results
    avgs = coll.get_averages(normalize=True)
    data = pd.DataFrame(avgs)
    pd.DataFrame.to_csv(data, "test.csv")
    print avgs
    print model.fp()
    # ------------- Retrieve the Data -------------#
    # 6-gene input signature
    #ALK = avgs.get('ALK')
    #MDK = avgs.get('MDK')
    #TrkA = avgs.get('TrkA')
    #NGF = avgs.get('NGF')
    #TrkB = avgs.get('TrkB')
    #BDNF = avgs.get('BDNF')

    # Model outcome states
    Differentiation = avgs.get('Differentiation')
    Apoptosis = avgs.get('Apoptosis')
    Proliferation = avgs.get('Proliferation')
    Angiogenesis = avgs.get('Angiogenesis')

    # Nodes at Issue
    #DNADamage = avgs.get('DNADamage')
    #p53 = avgs.get('p53')

    # Other Nodes
    #MDM2 = avgs.get('MDM2')
    #MAPK = avgs.get('MAPK')
    #p27 = avgs.get('p27')
    #FoxO = avgs.get('FoxO')
    #AKT = avgs.get('AKT')
    #Ras = avgs.get('Ras')
    #MYCN = avgs.get('MYCN')
    #MTOR = avgs.get('MTOR')
    #IP3 = avgs.get('IP3')

    ### Time axis (x)
    t = range(0, timecourse + 1)

    # Create plots with pre-defined labels. Try to make this a for loop
    fig, ax = plt.subplots()
    ax.plot(t, Differentiation, label='Differentiation')
    ax.plot(t, Apoptosis, label='Apoptosis')
    ax.plot(t, Angiogenesis, label='Angiogenesis')
    ax.plot(t, Proliferation, label='Proliferation')
    # ax.plot(t, TrkA, label='TrkA')
    # ax.plot(t, TrkB, label = 'TrkB')
    # ax.plot(t, MYCN, label = 'MYCN')
    # ax.plot(t, NGF, label = 'NGF')
    # ax.plot(t, MDK, label = 'MDK')
    # ax.plot(t, ALK, label = 'ALK')
    # ax.plot(t, Ras, label = 'Ras')
    # x.plot(t, AKT, label = 'AKT')
    # ax.plot(t, FoxO, label = 'FoxO')
    # ax.plot(t, p27, label = 'P27')
    # ax.plot(t, p53, label = 'P53')

    # Legends
    legend = ax.legend(loc=0, shadow=True, fontsize='medium')
    plt.xlabel('Iterations')
    plt.ylabel('On Proportion')
    plt.title("Asynchronous Updating Model")

    # beautify
    legend.get_frame().set_facecolor('#C0C0C0')

    # View Plot
    plt.show()

    return avgs
Ejemplo n.º 8
0
def all_ss_model_w_fix(str_file, str_mode, timecourse, csv_out_txt, geneon=[], geneoff=[], dumpevery=1000,\
                       nodes_for_averages=['Apoptosis', 'Proliferation', 'Angiogenesis', 'Differentiation']):
    '''
    A function to generate a list of all possible starting states of a model. Note that this function is a
    modification of the all_ss_model() function and allows for specific nodes to be fixed on or off.
    str_file: as a string, write the name of the file containing the rules for the boolean model
    str_mode: enter the mode of model that is going to be used, ie sync or async
    numnodes: the number of nodes in the model
    :return:
    A list of dictionaries with all possible starting states of the network.
    '''
    def dumper():
        '''
        will dump the data into a CSV file of name csv_out_txt
        :return: none
        '''
        with open(csv_out_txt, 'a') as csvfile:
            datadumper = csv.writer(csvfile, lineterminator='\n')
            for row in d:
                datadumper.writerow(row)
        return

    # header names for pandas dataframe and CSV data dump file
    headers = ['Index', 'CycleLength', 'CycleFingerprint', 'FirstState', 'LastState', str(nodes_for_averages[0]),\
               str(nodes_for_averages[1]), str(nodes_for_averages[2]), str(nodes_for_averages[3])]

    # Init a CSV to contain the data generated over the course of the run
    with open(csv_out_txt, 'wb') as out:
        csv_out = csv.writer(out)
        csv_out.writerow(headers)

    Bool1 = file(str_file).read()

    # Generate a new version of the model that allows the genes to be fixed on or off by removing updating rules for
    # them. This will not override the initialized value however, and that must be updated to be assigned on or off
    # separately.
    on = geneon
    off = geneoff
    Bool2 = tokenizer.modify_states(Bool1, on, off)
    model = Model(text=Bool2, mode=str_mode)
    initializer = state.all_initial_states(model.nodes, limit=None)

    # Utilized in the for loops for the loading bar
    load_status_divisor = len(model.nodes) - len(geneoff) - len(geneon)

    # The BooleanNet Data Collector. Here it is implemented to gather data on the states of the nodes in the model.
    coll = util.Collector()

    # Wiley Stoeber. 5/8/18. Create a modified version of the initializer that will pass over initial states that
    # contradict the gene set mode
    initializer_new = []

    if geneoff != [] or geneon != []:
        d = []
        p = 0
        n = 0
        for data_init in initializer:
            data = data_init[0]
            for i in range(len(geneoff)):
                if data[str(geneoff[i])]:
                    initializer_new.append(data_init)
            for i in range(len(geneon)):
                if data[str(geneon[i])]:
                    initializer_new.append(data_init)

        for data, initfunc in initializer_new:
            # Fixes genes on or off (True or False) at their starting state.
            for i in range(len(geneoff)):
                data.update({str(geneoff[i]): False})
            for i in range(len(geneon)):
                data.update({str(geneon[i]): True})

            # Initialize the model with the given pre-computed initial conditions stored in the data variable.
            # for a given model with Z nodes, there are 2 to the power of Z starting states.
            model.initialize(defaults=data)
            model.iterate(steps=timecourse)
            e = model.detect_cycles()
            nodes = nodes_for_averages

            d.append(list(model.detect_cycles()))
            d[p].append(model.fp())
            d[p].append(model.first)
            d[p].append(model.last)
            detect_states = 0 - e[1]
            coll.collect(states=model.states[detect_states:], nodes=nodes)
            # Console output for debugging and progress tracking
            print 'The fingerprint is', d[p][2]
            print 'The cycle length is', d[p][1]

            prc = ((n) / float(pow(2, load_status_divisor))) * 100
            print '%.2f' % prc + "% done."
            print '\n'

            # Iterate the model
            n += 1
            p += 1

            if p == dumpevery:
                dumper()
                p = 0
                d = []

        dumper()
        d = []
    else:
        d = []
        p = 0
        n = 0
        for data, initfunc in initializer:
            # Fixes genes on or off (True or False) at their starting state.
            for i in range(len(geneoff)):
                data.update({str(geneoff[i]): False})
            for i in range(len(geneon)):
                data.update({str(geneon[i]): True})

            # Initialize the model with the given pre-computed initial conditions stored in the data variable.
            # for a given model with Z nodes, there are 2 to the power of Z starting states.
            model.initialize(defaults=data)
            model.iterate(steps=timecourse)
            e = model.detect_cycles()
            nodes = nodes_for_averages

            d.append(list(model.detect_cycles()))
            d[p].append(model.fp())
            d[p].append(model.first)
            d[p].append(model.last)
            detect_states = 0 - e[1]
            coll.collect(states=model.states[detect_states:], nodes=nodes)

            # Console output for debugging and progress tracking
            print 'The fingerprint is', d[p][2]
            print 'The cycle length is', d[p][1]

            prc = ((n) / float(pow(2, load_status_divisor))) * 100
            print '%.2f' % prc + "% done."
            print '\n'

            # Iterate the model
            n += 1
            p += 1

            if p == dumpevery:
                dumper()
                p = 0
                d = []

        dumper()
        d = []

    # Generate a pandas dataframe of the dump file

    df = pd.read_csv(csv_out_txt)

    # Add the avg on state of the nodes specified by the user. They will output to new columns in row 1 of the data
    if nodes_for_averages != []:
        avgs = coll.get_averages(normalize=True)
        a1 = avgs[nodes_for_averages[0]]
        b1 = avgs[nodes_for_averages[1]]
        c1 = avgs[nodes_for_averages[2]]
        d1 = avgs[nodes_for_averages[3]]

        # Set the averages values in the dataframe.

        df.set_value(0, 'Apoptosis', a1[0])
        df.set_value(0, 'Proliferation', b1[0])
        df.set_value(0, 'Differentiation', d1[0])
        df.set_value(0, 'Angiogenesis', c1[0])

        df.to_csv(csv_out_txt)

    return df
Ejemplo n.º 9
0
def BoolModPlot(timecourse, samples, booldata, str_mode):
    coll = util.Collector()
    for i in range(samples):
        model = Model(text=booldata, mode=str_mode)
        model.initialize()
        model.iterate(steps=timecourse)
        print model.states

        # takes all nodes
        nodes = model.nodes
        coll.collect(states=model.states, nodes=nodes)

    # --------------- Detect Cycles ------------------#
    print model.report_cycles()
    # Return results
    avgs = coll.get_averages(normalize=True)
    data = pd.DataFrame(avgs)
    pd.DataFrame.to_csv(data, "test.csv")
    # ------------- Retrieve the Data -------------#
    # 6-gene input signature
    ALK = avgs.get('ALK')
    MDK = avgs.get('MDK')
    TrkA = avgs.get('TrkA')
    NGF = avgs.get('NGF')
    TrkB = avgs.get('TrkB')
    BDNF = avgs.get('BDNF')

    # Model outcome states
    Differentiation = avgs.get('Differentiation')
    Apoptosis = avgs.get('Apoptosis')
    Proliferation = avgs.get('Proliferation')
    Angiogenesis = avgs.get('Angiogenesis')

    # Nodes at Issue
    DNADamage = avgs.get('DNADamage')
    p53 = avgs.get('p53')

    # Other Nodes
    MDM2 = avgs.get('MDM2')
    MAPK = avgs.get('MAPK')
    p27 = avgs.get('p27')
    FoxO = avgs.get('FoxO')
    AKT = avgs.get('AKT')
    Ras = avgs.get('Ras')
    MYCN = avgs.get('MYCN')
    MTOR = avgs.get('MTOR')
    IP3 = avgs.get('IP3')

    ### Time axis (x)
    t = range(0, timecourse + 1)

    # Create plots with pre-defined labels. Try to make this a for loop
    fig, ax = plt.subplots()
    ax.plot(t, Differentiation, label='Differentiation')
    ax.plot(t, Apoptosis, label='Apoptosis')
    ax.plot(t, Angiogenesis, label='Angiogenesis')
    ax.plot(t, Proliferation, label='Proliferation')
    # ax.plot(t, TrkA, label='TrkA')
    # ax.plot(t, TrkB, label = 'TrkB')
    # ax.plot(t, MYCN, label = 'MYCN')
    # ax.plot(t, NGF, label = 'NGF')
    # ax.plot(t, MDK, label = 'MDK')
    # ax.plot(t, ALK, label = 'ALK')
    # ax.plot(t, Ras, label = 'Ras')
    # ax.plot(t, AKT, label = 'AKT')
    # ax.plot(t, FoxO, label = 'FoxO')
    # ax.plot(t, p27, label = 'P27')
    # ax.plot(t, p53, label = 'P53')

    # Legends
    legend = ax.legend(loc=0, shadow=True, fontsize='medium')
    plt.xlabel('Iterations')
    plt.ylabel('On Proportion')
    plt.title("Asynchronous Updating Model")

    # beautify
    legend.get_frame().set_facecolor('#C0C0C0')

    # View Plot
    plt.show()

    return avgs