예제 #1
0
def test_ele_load_uniform():
    osi = o3.OpenSeesInstance(ndm=2, state=3)
    ele_len = 2.0
    coords = [[0, 0], [ele_len, 0]]
    ele_nodes = [o3.node.Node(osi, *coords[x]) for x in range(len(coords))]
    transf = o3.geom_transf.Linear2D(osi, [])
    ele = o3.element.ElasticBeamColumn2D(osi,
                                         ele_nodes=ele_nodes,
                                         area=1.0,
                                         e_mod=1.0,
                                         iz=1.0,
                                         transf=transf,
                                         mass=1.0,
                                         c_mass="string")

    for i, node in enumerate(ele_nodes):
        if i == 0:
            o3.Fix3DOF(osi, node, o3.cc.FIXED, o3.cc.FIXED, o3.cc.FIXED)
        else:
            o3.Fix3DOF(osi, node, o3.cc.FREE, o3.cc.FIXED, o3.cc.FIXED)

    ts_po = o3.time_series.Linear(osi, factor=1)
    o3.pattern.Plain(osi, ts_po)
    udl = 10.
    o3.EleLoad2DUniform(osi, ele, w_y=-udl)

    tol = 1.0e-4
    o3.constraints.Plain(osi)
    o3.numberer.RCM(osi)
    o3.system.BandGeneral(osi)
    o3.test_check.NormDispIncr(osi, tol, 6)
    o3.algorithm.Newton(osi)
    n_steps_gravity = 1
    d_gravity = 1. / n_steps_gravity
    o3.integrator.LoadControl(osi, d_gravity, num_iter=10)
    o3.analysis.Static(osi)
    o3.analyze(osi, n_steps_gravity)
    opy.reactions()
    ele_loads = o3.get_ele_response(osi, ele, 'force')
    assert np.isclose(ele_loads[0], 0.0)
    assert np.isclose(ele_loads[1], udl * ele_len / 2)
    assert np.isclose(ele_loads[2], udl * ele_len**2 / 12)
    assert np.isclose(ele_loads[3], 0.0)
    assert np.isclose(ele_loads[4], udl * ele_len / 2)
    assert np.isclose(ele_loads[5], -udl * ele_len**2 / 12)
    assert np.isclose(o3.get_node_reaction(osi, ele_nodes[0], o3.cc.Y),
                      udl * ele_len / 2)
def get_inelastic_response(fb, asig, extra_time=0.0, xi=0.05, analysis_dt=0.001):
    """
    Run seismic analysis of a nonlinear FrameBuilding

    Parameters
    ----------
    fb: sfsimodels.Frame2DBuilding object
    asig: eqsig.AccSignal object
    extra_time
    xi
    analysis_dt

    Returns
    -------

    """
    osi = o3.OpenSeesInstance(ndm=2)

    q_floor = 10000.  # kPa
    trib_width = fb.floor_length
    trib_mass_per_length = q_floor * trib_width / 9.8

    # Establish nodes and set mass based on trib area
    # Nodes named as: C<column-number>-S<storey-number>, first column starts at C1-S0 = ground level left
    nd = OrderedDict()
    col_xs = np.cumsum(fb.bay_lengths)
    col_xs = np.insert(col_xs, 0, 0)
    n_cols = len(col_xs)
    sto_ys = fb.heights
    sto_ys = np.insert(sto_ys, 0, 0)
    for cc in range(1, n_cols + 1):
        for ss in range(fb.n_storeys + 1):
            nd[f"C{cc}-S{ss}"] = o3.node.Node(osi, col_xs[cc - 1], sto_ys[ss])

            if ss != 0:
                if cc == 1:
                    node_mass = trib_mass_per_length * fb.bay_lengths[0] / 2
                elif cc == n_cols:
                    node_mass = trib_mass_per_length * fb.bay_lengths[-1] / 2
                else:
                    node_mass = trib_mass_per_length * (fb.bay_lengths[cc - 2] + fb.bay_lengths[cc - 1] / 2)
                o3.set_node_mass(osi, nd[f"C{cc}-S{ss}"], node_mass, 0., 0.)

    # Set all nodes on a storey to have the same displacement
    for ss in range(0, fb.n_storeys + 1):
        for cc in range(1, n_cols + 1):
            o3.set_equal_dof(osi, nd[f"C1-S{ss}"], nd[f"C{cc}-S{ss}"], o3.cc.X)

    # Fix all base nodes
    for cc in range(1, n_cols + 1):
        o3.Fix3DOF(osi, nd[f"C{cc}-S0"], o3.cc.FIXED, o3.cc.FIXED, o3.cc.FIXED)

    # Coordinate transformation
    transf = o3.geom_transf.Linear2D(osi, [])

    l_hinge = fb.bay_lengths[0] * 0.1

    # Define material
    e_conc = 30.0e6
    i_beams = 0.4 * fb.beam_widths * fb.beam_depths ** 3 / 12
    i_columns = 0.5 * fb.column_widths * fb.column_depths ** 3 / 12
    a_beams = fb.beam_widths * fb.beam_depths
    a_columns = fb.column_widths * fb.column_depths
    ei_beams = e_conc * i_beams
    ei_columns = e_conc * i_columns
    eps_yield = 300.0e6 / 200e9
    phi_y_col = calc_yield_curvature(fb.column_depths, eps_yield)
    phi_y_beam = calc_yield_curvature(fb.beam_depths, eps_yield) * 10  # TODO: re-evaluate

    # Define beams and columns
    # Columns named as: C<column-number>-S<storey-number>, first column starts at C1-S0 = ground floor left
    # Beams named as: B<bay-number>-S<storey-number>, first beam starts at B1-S1 = first storey left (foundation at S0)

    md = OrderedDict()  # material dict
    sd = OrderedDict()  # section dict
    ed = OrderedDict()  # element dict

    for ss in range(fb.n_storeys):

        # set columns
        for cc in range(1, fb.n_cols + 1):
            lp_i = 0.4
            lp_j = 0.4  # plastic hinge length
            ele_str = f"C{cc}-S{ss}S{ss+1}"

            top_sect = o3.section.Elastic2D(osi, e_conc, a_columns[ss][cc - 1], i_columns[ss][cc - 1])
            bot_sect = o3.section.Elastic2D(osi, e_conc, a_columns[ss][cc - 1], i_columns[ss][cc - 1])
            centre_sect = o3.section.Elastic2D(osi, e_conc, a_columns[ss][cc - 1], i_columns[ss][cc - 1])
            sd[ele_str + "T"] = top_sect
            sd[ele_str + "B"] = bot_sect
            sd[ele_str + "C"] = centre_sect

            integ = o3.beam_integration.HingeMidpoint(osi, bot_sect, lp_i, top_sect, lp_j, centre_sect)

            bot_node = nd[f"C{cc}-S{ss}"]
            top_node = nd[f"C{cc}-S{ss+1}"]
            ed[ele_str] = o3.element.ForceBeamColumn(osi, [bot_node, top_node], transf, integ)

        # Set beams
        for bb in range(1, fb.n_bays + 1):
            lp_i = 0.5
            lp_j = 0.5
            ele_str = f"C{bb-1}C{bb}-S{ss}"

            mat = o3.uniaxial_material.ElasticBilin(osi, ei_beams[ss][bb - 1], 0.05 * ei_beams[ss][bb - 1], phi_y_beam[ss][bb - 1])
            md[ele_str] = mat
            left_sect = o3.section.Uniaxial(osi, mat, quantity=o3.cc.M_Z)
            right_sect = o3.section.Uniaxial(osi, mat, quantity=o3.cc.M_Z)
            centre_sect = o3.section.Elastic2D(osi, e_conc, a_beams[ss][bb - 1], i_beams[ss][bb - 1])
            integ = o3.beam_integration.HingeMidpoint(osi, left_sect, lp_i, right_sect, lp_j, centre_sect)

            left_node = nd[f"C{bb}-S{ss+1}"]
            right_node = nd[f"C{bb+1}-S{ss+1}"]
            ed[ele_str] = o3.element.ForceBeamColumn(osi, [left_node, right_node], transf, integ)

    # Define the dynamic analysis
    a_series = o3.time_series.Path(osi, dt=asig.dt, values=-1 * asig.values)  # should be negative
    o3.pattern.UniformExcitation(osi, dir=o3.cc.X, accel_series=a_series)

    # set damping based on first eigen mode
    angular_freq_sqrd = o3.get_eigen(osi, solver='fullGenLapack', n=1)
    if hasattr(angular_freq_sqrd, '__len__'):
        angular_freq = angular_freq_sqrd[0] ** 0.5
    else:
        angular_freq = angular_freq_sqrd ** 0.5
    if isinstance(angular_freq, complex):
        raise ValueError("Angular frequency is complex, issue with stiffness or mass")
    beta_k = 2 * xi / angular_freq
    o3.rayleigh.Rayleigh(osi, alpha_m=0.0, beta_k=beta_k, beta_k_init=0.0, beta_k_comm=0.0)

    # Run the dynamic analysis

    o3.wipe_analysis(osi)

    o3.algorithm.Newton(osi)
    o3.system.SparseGeneral(osi)
    o3.numberer.RCM(osi)
    o3.constraints.Transformation(osi)
    o3.integrator.Newmark(osi, 0.5, 0.25)
    o3.analysis.Transient(osi)

    tol = 1.0e-4
    iter = 4
    o3.test_check.EnergyIncr(osi, tol, iter, 0, 2)
    analysis_time = (len(asig.values) - 1) * asig.dt + extra_time
    outputs = {
        "time": [],
        "rel_disp": [],
        "rel_accel": [],
        "rel_vel": [],
        "force": [],
        "ele_mom": [],
        "ele_curve": [],
    }
    print("Analysis starting")
    while o3.get_time(osi) < analysis_time:
        curr_time = opy.getTime()
        o3.analyze(osi, 1, analysis_dt)
        outputs["time"].append(curr_time)
        outputs["rel_disp"].append(o3.get_node_disp(osi, nd["C%i-S%i" % (1, fb.n_storeys)], o3.cc.X))
        outputs["rel_vel"].append(o3.get_node_vel(osi, nd["C%i-S%i" % (1, fb.n_storeys)], o3.cc.X))
        outputs["rel_accel"].append(o3.get_node_accel(osi, nd["C%i-S%i" % (1, fb.n_storeys)], o3.cc.X))
        # outputs['ele_mom'].append(opy.eleResponse('-ele', [ed['B%i-S%i' % (1, 0)], 'basicForce']))
        o3.gen_reactions(osi)
        react = 0
        for cc in range(1, fb.n_cols):
            react += -o3.get_node_reaction(osi, nd["C%i-S%i" % (cc, 0)], o3.cc.X)
        outputs["force"].append(react)  # Should be negative since diff node
    o3.wipe(osi)
    for item in outputs:
        outputs[item] = np.array(outputs[item])

    return outputs
예제 #3
0
def get_inelastic_response(mass,
                           k_spring,
                           f_yield,
                           motion,
                           dt,
                           xi=0.05,
                           r_post=0.0):
    """
    Run seismic analysis of a nonlinear SDOF

    :param mass: SDOF mass
    :param k_spring: spring stiffness
    :param f_yield: yield strength
    :param motion: list, acceleration values
    :param dt: float, time step of acceleration values
    :param xi: damping ratio
    :param r_post: post-yield stiffness
    :return:
    """
    osi = o3.OpenSeesInstance(ndm=2)

    # Establish nodes
    bot_node = o3.node.Node(osi, 0, 0)
    top_node = o3.node.Node(osi, 0, 0)

    # Fix bottom node
    o3.Fix3DOF(osi, top_node, o3.cc.FREE, o3.cc.FIXED, o3.cc.FIXED)
    o3.Fix3DOF(osi, bot_node, o3.cc.FIXED, o3.cc.FIXED, o3.cc.FIXED)
    # Set out-of-plane DOFs to be slaved
    o3.EqualDOF(osi, top_node, bot_node, [o3.cc.Y, o3.cc.DOF2D_ROTZ])

    # nodal mass (weight / g):
    o3.Mass(osi, top_node, mass, 0., 0.)

    # Define material
    bilinear_mat = o3.uniaxial_material.Steel01(osi,
                                                fy=f_yield,
                                                e0=k_spring,
                                                b=r_post)

    # Assign zero length element, # Note: pass actual node and material objects into element
    o3.element.ZeroLength(osi, [bot_node, top_node],
                          mats=[bilinear_mat],
                          dirs=[o3.cc.DOF2D_X],
                          r_flag=1)

    # Define the dynamic analysis
    values = list(-1 * motion)  # should be negative
    acc_series = o3.time_series.Path(osi, dt, values)
    o3.pattern.UniformExcitation(osi, o3.cc.X, accel_series=acc_series)

    # set damping based on first eigen mode
    angular_freq2 = o3.get_eigen(osi, solver='fullGenLapack', n=1)
    if hasattr(angular_freq2, '__len__'):
        angular_freq2 = angular_freq2[0]
    angular_freq = angular_freq2**0.5
    beta_k = 2 * xi / angular_freq
    o3.rayleigh.Rayleigh(osi,
                         alpha_m=0.0,
                         beta_k=beta_k,
                         beta_k_init=0.0,
                         beta_k_comm=0.0)

    # Run the dynamic analysis

    o3.wipe_analysis(osi)
    newmark_gamma = 0.5
    newmark_beta = 0.25

    o3.algorithm.Newton(osi)
    o3.constraints.Transformation(osi)
    o3.algorithm.Newton(osi)
    o3.numberer.RCM(osi)
    o3.system.SparseGeneral(osi)
    o3.integrator.Newmark(osi, newmark_gamma, newmark_beta)
    o3.analysis.Transient(osi)

    o3.test_check.EnergyIncr(osi, tol=1.0e-10, max_iter=10)
    analysis_time = (len(values) - 1) * dt
    analysis_dt = 0.001
    outputs = {"time": [], "rel_disp": [], "rel_accel": [], "force": []}
    o3.record(osi)
    curr_time = o3.get_time(osi)
    while curr_time < analysis_time:
        outputs["time"].append(curr_time)
        outputs["rel_disp"].append(o3.get_node_disp(osi, top_node, o3.cc.X))
        outputs["rel_accel"].append(o3.get_node_accel(osi, top_node, o3.cc.X))
        o3.gen_reactions(osi)
        outputs["force"].append(-o3.get_node_reaction(
            osi, bot_node, o3.cc.X))  # Negative since diff node
        o3.analyze(osi, 1, analysis_dt)
        curr_time = o3.get_time(osi)
    o3.wipe(osi)
    for item in outputs:
        outputs[item] = np.array(outputs[item])

    return outputs
def run_ts_custom_strain(mat, esig_v0, strains, osi=None, nu_dyn=None, target_d_inc=0.00001, k0=None, etype='newmark_explicit',
                         handle='silent', verbose=0, opyfile=None, dss=False, plain_strain=True, min_n=10, nl=True):
    # if dss:
    #     raise ValueError('dss option is not working')
    damp = 0.05
    omega0 = 0.2
    omega1 = 20.0
    a1 = 2. * damp / (omega0 + omega1)
    a0 = a1 * omega0 * omega1
    if osi is None:
        osi = o3.OpenSeesInstance(ndm=2, ndf=2)
        mat.build(osi)

    # Establish nodes
    h_ele = 1.
    nodes = [
        o3.node.Node(osi, 0.0, 0.0),
        o3.node.Node(osi, h_ele, 0.0),
        o3.node.Node(osi, h_ele, h_ele),
        o3.node.Node(osi, 0.0, h_ele)
    ]

    # Fix bottom node
    o3.Fix2DOF(osi, nodes[0], o3.cc.FIXED, o3.cc.FIXED)
    if k0 is None:
        o3.Fix2DOF(osi, nodes[1], o3.cc.FIXED, o3.cc.FIXED)
        # Set out-of-plane DOFs to be slaved
        o3.EqualDOF(osi, nodes[2], nodes[3], [o3.cc.X, o3.cc.Y])
    else:  # control k0 with node forces
        o3.Fix2DOF(osi, nodes[1], o3.cc.FIXED, o3.cc.FREE)

    if plain_strain:
        oop = 'PlaneStrain'
    else:
        oop = 'PlaneStress'

    ele = o3.element.SSPquad(osi, nodes, mat, oop, 1, 0.0, 0.0)

    angular_freqs = np.array(o3.get_eigen(osi, solver='fullGenLapack', n=2)) ** 0.5
    print('angular_freqs: ', angular_freqs)
    periods = 2 * np.pi / angular_freqs
    xi = 0.03
    o3.ModalDamping(osi, [xi, xi])

    print('periods: ', periods)
    o3.constraints.Transformation(osi)
    o3.test_check.NormDispIncr(osi, tol=1.0e-6, max_iter=35, p_flag=0)
    o3.numberer.RCM(osi)
    if etype == 'implicit':
        o3.algorithm.Newton(osi)
        o3.system.FullGeneral(osi)
        o3.integrator.Newmark(osi, gamma=0.5, beta=0.25)
        dt = 0.01
    else:
        o3.algorithm.Linear(osi, factor_once=True)
        o3.system.FullGeneral(osi)
        if etype == 'newmark_explicit':
            o3.integrator.NewmarkExplicit(osi, gamma=0.5)
            explicit_dt = periods[0] / np.pi / 8
        elif etype == 'central_difference':
            o3.integrator.CentralDifference(osi)
            explicit_dt = periods[0] / np.pi / 16  # 0.5 is a factor of safety
        elif etype == 'hht_explicit':
            o3.integrator.HHTExplicit(osi, alpha=0.5)
            explicit_dt = periods[0] / np.pi / 8
        elif etype == 'explicit_difference':
            o3.integrator.ExplicitDifference(osi)
            explicit_dt = periods[0] / np.pi / 4
        else:
            raise ValueError(etype)
        print('explicit_dt: ', explicit_dt)
        dt = explicit_dt
    o3.analysis.Transient(osi)

    o3.update_material_stage(osi, mat, stage=0)

    # dt = 0.00001
    tload = 60
    n_steps = tload / dt
    # Add static vertical pressure and stress bias
    time_series = o3.time_series.Path(osi, time=[0, tload, 1e10], values=[0, 1, 1])
    o3.pattern.Plain(osi, time_series)
    # ts0 = o3.time_series.Linear(osi, factor=1)
    # o3.pattern.Plain(osi, ts0)

    if k0:
        o3.Load(osi, nodes[2], [-esig_v0 / 2, -esig_v0 / 2])
        o3.Load(osi, nodes[3], [esig_v0 / 2, -esig_v0 / 2])
        o3.Load(osi, nodes[1], [-esig_v0 / 2, 0])
        # node 0 is fixed
    else:
        o3.Load(osi, nodes[2], [0, -esig_v0 / 2])
        o3.Load(osi, nodes[3], [0, -esig_v0 / 2])

    print('Apply init stress to elastic element')
    o3.analyze(osi, num_inc=n_steps, dt=dt)
    stresses = o3.get_ele_response(osi, ele, 'stress')
    print('init_stress0: ', stresses)
    o3.load_constant(osi, tload)

    if hasattr(mat, 'update_to_nonlinear') and nl:
        print('set to nonlinear')
        mat.update_to_nonlinear()
        o3.analyze(osi, 10000, dt=dt)
    # if not nl:
    #     mat.update_to_linear()
    if nu_dyn is not None:
        mat.set_nu(nu_dyn, eles=[ele])
        o3.analyze(osi, 10000, dt=dt)

    # o3.extensions.to_py_file(osi)
    stresses = o3.get_ele_response(osi, ele, 'stress')
    print('init_stress1: ', stresses)

    # Prepare for reading results
    exit_code = None
    stresses = o3.get_ele_response(osi, ele, 'stress')
    if dss:
        o3.gen_reactions(osi)
        force0 = o3.get_node_reaction(osi, nodes[2], o3.cc.DOF2D_X)
        force1 = o3.get_node_reaction(osi, nodes[3], o3.cc.DOF2D_X)
        # force2 = o3.get_node_reaction(osi, nodes[0], o3.cc.DOF2D_X)
        stress = [force1 + force0]
        strain = [o3.get_node_disp(osi, nodes[2], dof=o3.cc.DOF2D_X)]
        sxy_ind = None
        gxy_ind = None
        # iforce0 = o3.get_node_reaction(osi, nodes[0], o3.cc.DOF2D_X)
        # iforce1 = o3.get_node_reaction(osi, nodes[1], o3.cc.DOF2D_X)
        # iforce2 = o3.get_node_reaction(osi, nodes[2], o3.cc.DOF2D_X)
        # iforce3 = o3.get_node_reaction(osi, nodes[3], o3.cc.DOF2D_X)
        # print(iforce0, iforce1, iforce2, iforce3, stresses[2])
    else:
        ro = o3.recorder.load_recorder_options()
        import pandas as pd
        df = pd.read_csv(ro)
        mat_type = ele.mat.type
        dfe = df[(df['mat'] == mat_type) & (df['form'] == oop)]
        df_sxy = dfe[dfe['recorder'] == 'stress']
        outs = df_sxy['outs'].iloc[0].split('-')
        sxy_ind = outs.index('sxy')

        df_gxy = dfe[dfe['recorder'] == 'strain']
        outs = df_gxy['outs'].iloc[0].split('-')
        gxy_ind = outs.index('gxy')
        stress = [stresses[sxy_ind]]
        cur_strains = o3.get_ele_response(osi, ele, 'strain')
        strain = [cur_strains[gxy_ind]]

    time_series = o3.time_series.Path(osi, time=[0, tload, 1e10], values=[0, 1, 1])
    o3.pattern.Plain(osi, time_series)
    disps = list(np.array(strains) * 1)
    d_per_dt = 0.01
    diff_disps = np.diff(disps, prepend=0)
    time_incs = np.abs(diff_disps) / d_per_dt
    approx_n_steps = time_incs / dt
    time_incs = np.where(approx_n_steps < 800, 800 * dt, time_incs)
    approx_n_steps = time_incs / dt
    assert min(approx_n_steps) >= 8, approx_n_steps
    curr_time = o3.get_time(osi)
    times = list(np.cumsum(time_incs) + curr_time)
    disps.append(disps[-1])
    times.append(1e10)

    disps = list(disps)
    n_steps_p2 = int((times[-2] - curr_time) / dt) + 10

    print('n_steps: ', n_steps_p2)
    times.insert(0, curr_time)
    disps.insert(0, 0.0)

    init_disp = o3.get_node_disp(osi, nodes[2], dof=o3.cc.X)

    disps = list(np.array(disps) + init_disp)
    ts0 = o3.time_series.Path(osi, time=times, values=disps, factor=1)
    pat0 = o3.pattern.Plain(osi, ts0)
    o3.SP(osi, nodes[2], dof=o3.cc.X, dof_values=[1])
    o3.SP(osi, nodes[3], dof=o3.cc.X, dof_values=[1])
    print('init_disp: ', init_disp)
    print('path -times: ', times)
    print('path -values: ', disps)

    v_eff = [stresses[1]]
    h_eff = [stresses[0]]
    time = [o3.get_time(osi)]
    for i in range(int(n_steps_p2 / 200)):
        print(i / (n_steps_p2 / 200))
        fail = o3.analyze(osi, 200, dt=dt)
        o3.gen_reactions(osi)

        stresses = o3.get_ele_response(osi, ele, 'stress')
        v_eff.append(stresses[1])
        h_eff.append(stresses[0])
        if dss:
            o3.gen_reactions(osi)
            force0 = o3.get_node_reaction(osi, nodes[2], o3.cc.DOF2D_X)
            force1 = o3.get_node_reaction(osi, nodes[3], o3.cc.DOF2D_X)
            stress.append(force1 + force0)
            strain.append(o3.get_node_disp(osi, nodes[2], dof=o3.cc.DOF2D_X))
        else:
            stress.append(stresses[sxy_ind])
            cur_strains = o3.get_ele_response(osi, ele, 'strain')
            strain.append(cur_strains[gxy_ind])
        time.append(o3.get_time(osi))

        if fail:
            break

    return np.array(stress), np.array(strain)-init_disp, np.array(v_eff), np.array(h_eff), np.array(time), exit_code
def run_analysis(asig, period, xi, f_yield, etype):
    # Load a ground motion

    # Define inelastic SDOF
    mass = 1.0

    r_post = 0.0

    # Initialise OpenSees instance
    osi = o3.OpenSeesInstance(ndm=2, state=0)

    # Establish nodes
    bot_node = o3.node.Node(osi, 0, 0)
    top_node = o3.node.Node(osi, 0, 0)

    # Fix bottom node
    o3.Fix3DOF(osi, top_node, o3.cc.FREE, o3.cc.FIXED, o3.cc.FIXED)
    o3.Fix3DOF(osi, bot_node, o3.cc.FIXED, o3.cc.FIXED, o3.cc.FIXED)
    # Set out-of-plane DOFs to be slaved
    o3.EqualDOF(osi, top_node, bot_node, [o3.cc.Y, o3.cc.ROTZ])

    # nodal mass (weight / g):
    o3.Mass(osi, top_node, mass, 0., 0.)

    # Define material
    k_spring = 4 * np.pi**2 * mass / period**2
    bilinear_mat = o3.uniaxial_material.Steel01(osi,
                                                fy=f_yield,
                                                e0=k_spring,
                                                b=r_post)

    # Assign zero length element, # Note: pass actual node and material objects into element
    o3.element.ZeroLength(osi, [bot_node, top_node],
                          mats=[bilinear_mat],
                          dirs=[o3.cc.DOF2D_X],
                          r_flag=1)

    # Define the dynamic analysis

    # Define the dynamic analysis
    acc_series = o3.time_series.Path(osi, dt=asig.dt, values=-1 *
                                     asig.values)  # should be negative
    o3.pattern.UniformExcitation(osi, dir=o3.cc.X, accel_series=acc_series)

    # set damping based on first eigen mode
    angular_freqs = np.array(o3.get_eigen(osi, solver='fullGenLapack',
                                          n=1))**0.5
    beta_k = 2 * xi / angular_freqs[0]
    print('angular_freqs: ', angular_freqs)
    periods = 2 * np.pi / angular_freqs

    o3.rayleigh.Rayleigh(osi,
                         alpha_m=0.0,
                         beta_k=beta_k,
                         beta_k_init=0.0,
                         beta_k_comm=0.0)

    # Run the dynamic analysis
    o3.wipe_analysis(osi)

    # Run the dynamic analysis
    o3.constraints.Transformation(osi)
    o3.test_check.NormDispIncr(osi, tol=1.0e-6, max_iter=35, p_flag=0)
    o3.numberer.RCM(osi)
    if etype == 'implicit':
        o3.algorithm.Newton(osi)
        o3.system.SparseGeneral(osi)
        o3.integrator.Newmark(osi, gamma=0.5, beta=0.25)
        analysis_dt = 0.01
    else:
        o3.algorithm.Linear(osi, factor_once=True)
        o3.system.FullGeneral(osi)
        if etype == 'newmark_explicit':
            o3.integrator.NewmarkExplicit(osi, gamma=0.6)
            explicit_dt = periods[0] / np.pi / 32
        elif etype == 'central_difference':
            o3.integrator.CentralDifference(osi)
            o3.opy.integrator('HHTExplicit')
            explicit_dt = periods[0] / np.pi / 16  # 0.5 is a factor of safety
        elif etype == 'explicit_difference':
            o3.integrator.ExplicitDifference(osi)
            explicit_dt = periods[0] / np.pi / 32
        else:
            raise ValueError(etype)
        print('explicit_dt: ', explicit_dt)
        analysis_dt = explicit_dt
    o3.analysis.Transient(osi)

    analysis_time = asig.time[-1]

    outputs = {
        "time": [],
        "rel_disp": [],
        "rel_accel": [],
        "rel_vel": [],
        "force": []
    }

    while o3.get_time(osi) < analysis_time:
        o3.analyze(osi, 1, analysis_dt)
        curr_time = o3.get_time(osi)
        outputs["time"].append(curr_time)
        outputs["rel_disp"].append(o3.get_node_disp(osi, top_node, o3.cc.X))
        outputs["rel_vel"].append(o3.get_node_vel(osi, top_node, o3.cc.X))
        outputs["rel_accel"].append(o3.get_node_accel(osi, top_node, o3.cc.X))
        o3.gen_reactions(osi)
        outputs["force"].append(-o3.get_node_reaction(
            osi, bot_node, o3.cc.X))  # Negative since diff node
    o3.wipe(osi)
    for item in outputs:
        outputs[item] = np.array(outputs[item])
    return outputs
def run(show=0):
    # Load a ground motion
    record_filename = 'test_motion_dt0p01.txt'
    asig = eqsig.load_asig(ap.MODULE_DATA_PATH + 'gms/' + record_filename,
                           m=0.5)

    # Define inelastic SDOF
    period = 1.0
    xi = 0.05
    mass = 1.0
    f_yield = 1.5  # Reduce this to make it nonlinear
    r_post = 0.0

    # Initialise OpenSees instance
    osi = o3.OpenSeesInstance(ndm=2, state=0)

    # Establish nodes
    bot_node = o3.node.Node(osi, 0, 0)
    top_node = o3.node.Node(osi, 0, 0)

    # Fix bottom node
    o3.Fix3DOF(osi, top_node, o3.cc.FREE, o3.cc.FIXED, o3.cc.FIXED)
    o3.Fix3DOF(osi, bot_node, o3.cc.FIXED, o3.cc.FIXED, o3.cc.FIXED)
    # Set out-of-plane DOFs to be slaved
    o3.EqualDOF(osi, top_node, bot_node, [o3.cc.Y, o3.cc.ROTZ])

    # nodal mass (weight / g):
    o3.Mass(osi, top_node, mass, 0., 0.)

    # Define material
    k_spring = 4 * np.pi**2 * mass / period**2
    bilinear_mat = o3.uniaxial_material.Steel01(osi,
                                                fy=f_yield,
                                                e0=k_spring,
                                                b=r_post)

    # Assign zero length element, # Note: pass actual node and material objects into element
    o3.element.ZeroLength(osi, [bot_node, top_node],
                          mats=[bilinear_mat],
                          dirs=[o3.cc.DOF2D_X],
                          r_flag=1)

    # Define the dynamic analysis

    # Define the dynamic analysis
    acc_series = o3.time_series.Path(osi, dt=asig.dt, values=-1 *
                                     asig.values)  # should be negative
    o3.pattern.UniformExcitation(osi, dir=o3.cc.X, accel_series=acc_series)

    # set damping based on first eigen mode
    angular_freq = o3.get_eigen(osi, solver='fullGenLapack', n=1)[0]**0.5
    beta_k = 2 * xi / angular_freq
    o3.rayleigh.Rayleigh(osi,
                         alpha_m=0.0,
                         beta_k=beta_k,
                         beta_k_init=0.0,
                         beta_k_comm=0.0)

    # Run the dynamic analysis
    o3.wipe_analysis(osi)

    # Run the dynamic analysis
    o3.algorithm.Newton(osi)
    o3.system.SparseGeneral(osi)
    o3.numberer.RCM(osi)
    o3.constraints.Transformation(osi)
    o3.integrator.Newmark(osi, gamma=0.5, beta=0.25)
    o3.analysis.Transient(osi)

    o3.test_check.EnergyIncr(osi, tol=1.0e-10, max_iter=10)
    analysis_time = asig.time[-1]
    analysis_dt = 0.001
    outputs = {
        "time": [],
        "rel_disp": [],
        "rel_accel": [],
        "rel_vel": [],
        "force": []
    }

    while o3.get_time(osi) < analysis_time:
        o3.analyze(osi, 1, analysis_dt)
        curr_time = o3.get_time(osi)
        outputs["time"].append(curr_time)
        outputs["rel_disp"].append(o3.get_node_disp(osi, top_node, o3.cc.X))
        outputs["rel_vel"].append(o3.get_node_vel(osi, top_node, o3.cc.X))
        outputs["rel_accel"].append(o3.get_node_accel(osi, top_node, o3.cc.X))
        o3.gen_reactions(osi)
        outputs["force"].append(-o3.get_node_reaction(
            osi, bot_node, o3.cc.X))  # Negative since diff node
    o3.wipe(osi)
    for item in outputs:
        outputs[item] = np.array(outputs[item])

    if show:
        import matplotlib.pyplot as plt
        plt.plot(outputs['time'], outputs['rel_disp'], label='o3seespy')
        periods = np.array([period])

        # Compare closed form elastic solution
        from eqsig import sdof
        resp_u, resp_v, resp_a = sdof.response_series(motion=asig.values,
                                                      dt=asig.dt,
                                                      periods=periods,
                                                      xi=xi)
        plt.plot(asig.time, resp_u[0], ls='--', label='Elastic')
        plt.legend()
        plt.show()
예제 #7
0
def run_2d_strain_driver_iso(osi, base_mat, esig_v0, disps, target_d_inc=0.00001, max_steps=10000, handle='silent', da_strain_max=0.05, max_cycles=200, srate=0.0001, esig_v_min=1.0, k0_init=1, verbose=0,
                   cyc_lim_fail=True):
    if not np.isclose(k0_init, 1., rtol=0.05):
        raise ValueError(f'Only supports k0=1, current k0={k0_init:.3f}')
    max_steps_per_half_cycle = 50000

    nodes = [
        o3.node.Node(osi, 0.0, 0.0),
        o3.node.Node(osi, 1.0, 0.0),
        o3.node.Node(osi, 1.0, 1.0),
        o3.node.Node(osi, 0.0, 1.0)
    ]
    for node in nodes:
        o3.Fix2DOF(osi, node, 1, 1)

    mat = o3.nd_material.InitStressNDMaterial(osi, other=base_mat, init_stress=-esig_v0, n_dim=2)

    ele = o3.element.SSPquad(osi, nodes, mat, 'PlaneStrain', 1, 0.0, 0.0)

    # create analysis
    o3.constraints.Penalty(osi, 1.0e15, 1.0e15)
    o3.algorithm.Linear(osi)
    o3.numberer.RCM(osi)
    o3.system.FullGeneral(osi)
    o3.analysis.Static(osi)

    d_init = 0.0
    d_max = 0.1  # element height is 1m
    max_time = (d_max - d_init) / srate

    ts0 = o3.time_series.Linear(osi, factor=1)
    o3.pattern.Plain(osi, ts0)
    o3.Load(osi, nodes[2], [1.0, 0.0])
    o3.Load(osi, nodes[3], [1.0, 0.0])

    o3.analyze(osi, 1)
    o3.set_parameter(osi, value=1, eles=[ele], args=['materialState'])
    o3.update_material_stage(osi, base_mat, 1)
    o3.analyze(osi, 1)

    exit_code = None
    # loop through the total number of cycles
    react = 0
    strain = [0]
    stresses = o3.get_ele_response(osi, ele, 'stress')
    stress = [stresses[2]]
    v_eff = [stresses[1]]
    h_eff = [stresses[0]]
    d_incs = np.diff(disps, prepend=0)
    # orys = np.where(diffs >= 0, 1, -1)
    for i in range(len(disps)):
        d_inc_i = d_incs[i]
        if target_d_inc < abs(d_inc_i):
            n = int(abs(d_inc_i / target_d_inc))
            d_step = d_inc_i / n
        else:
            n = 1
            d_step = d_inc_i
        for j in range(n):
            o3.integrator.DisplacementControl(osi, nodes[2], o3.cc.DOF2D_X, -d_step)
            o3.Load(osi, nodes[2], [1.0, 0.0])
            o3.Load(osi, nodes[3], [1.0, 0.0])
            o3.analyze(osi, 1)
            o3.gen_reactions(osi)
            # react = o3.get_ele_response(osi, ele, 'force')[0]
            stresses = o3.get_ele_response(osi, ele, 'stress')
            v_eff.append(stresses[1])
            h_eff.append(stresses[0])
            force0 = o3.get_node_reaction(osi, nodes[0], o3.cc.DOF2D_X)
            force1 = o3.get_node_reaction(osi, nodes[1], o3.cc.DOF2D_X)
            stress.append(-force0 - force1)
            # stress.append(stresses[2])
            end_strain = -o3.get_node_disp(osi, nodes[2], dof=o3.cc.DOF2D_X)
            strain.append(end_strain)

    return -np.array(stress), np.array(strain), np.array(v_eff), np.array(h_eff), exit_code
예제 #8
0
def run_2d_strain_driver(osi, mat, esig_v0, disps, target_d_inc=0.00001, handle='silent', verbose=0):
    k0 = 1.0
    pois = k0 / (1 + k0)
    damp = 0.05
    omega0 = 0.2
    omega1 = 20.0
    a1 = 2. * damp / (omega0 + omega1)
    a0 = a1 * omega0 * omega1

    # Establish nodes
    h_ele = 1.
    nodes = [
        o3.node.Node(osi, 0.0, 0.0),
        o3.node.Node(osi, h_ele, 0.0),
        o3.node.Node(osi, h_ele, h_ele),
        o3.node.Node(osi, 0.0, h_ele)
    ]

    # Fix bottom node
    o3.Fix2DOF(osi, nodes[0], o3.cc.FIXED, o3.cc.FIXED)
    o3.Fix2DOF(osi, nodes[1], o3.cc.FIXED, o3.cc.FIXED)
    o3.Fix2DOF(osi, nodes[2], o3.cc.FREE, o3.cc.FREE)
    o3.Fix2DOF(osi, nodes[3], o3.cc.FREE, o3.cc.FREE)
    # Set out-of-plane DOFs to be slaved
    o3.EqualDOF(osi, nodes[2], nodes[3], [o3.cc.X, o3.cc.Y])

    ele = o3.element.SSPquad(osi, nodes, mat, 'PlaneStrain', 1, 0.0, 0.0)

    o3.constraints.Transformation(osi)
    o3.test_check.NormDispIncr(osi, tol=1.0e-3, max_iter=35, p_flag=0)
    o3.algorithm.Newton(osi)
    o3.numberer.RCM(osi)
    o3.system.FullGeneral(osi)
    o3.integrator.DisplacementControl(osi, nodes[2], o3.cc.DOF2D_Y, 0.005)
    # o3.rayleigh.Rayleigh(osi, a0, a1, 0.0, 0.0)
    o3.analysis.Static(osi)
    o3.update_material_stage(osi, mat, stage=0)

    # Add static vertical pressure and stress bias
    # time_series = o3.time_series.Path(osi, time=[0, 100, 1e10], values=[0, 1, 1])
    # o3.pattern.Plain(osi, time_series)
    ts0 = o3.time_series.Linear(osi, factor=1)
    o3.pattern.Plain(osi, ts0)
    o3.Load(osi, nodes[2], [0, -esig_v0 / 2])
    o3.Load(osi, nodes[3], [0, -esig_v0 / 2])

    o3.analyze(osi, num_inc=100)
    stresses = o3.get_ele_response(osi, ele, 'stress')
    print('init_stress0: ', stresses)

    # ts2 = o3.time_series.Path(osi, time=[110, 80000, 1e10], values=[1., 1., 1.], factor=1)
    # o3.pattern.Plain(osi, ts2, fact=1.)
    # y_vert = o3.get_node_disp(osi, nodes[2], o3.cc.Y)
    # o3.SP(osi, nodes[3], dof=o3.cc.Y, dof_values=[y_vert])
    # o3.SP(osi, nodes[2], dof=o3.cc.Y, dof_values=[y_vert])
    #
    # o3.analyze(osi, 25, dt=1)

    o3.wipe_analysis(osi)
    o3.constraints.Transformation(osi)
    o3.test_check.NormDispIncr(osi, tol=1.0e-6, max_iter=35, p_flag=0)
    o3.algorithm.Newton(osi)
    o3.numberer.RCM(osi)
    o3.system.FullGeneral(osi)
    o3.analysis.Static(osi)

    o3.update_material_stage(osi, mat, stage=1)
    o3.analyze(osi, 25, dt=1)
    # o3.set_parameter(osi, value=sl.poissons_ratio, eles=[ele], args=['poissonRatio', 1])

    # o3.extensions.to_py_file(osi)
    stresses = o3.get_ele_response(osi, ele, 'stress')
    print('init_stress1: ', stresses)

    exit_code = None
    strain = [0]
    stresses = o3.get_ele_response(osi, ele, 'stress')
    force0 = o3.get_node_reaction(osi, nodes[0], o3.cc.DOF2D_X)
    force1 = o3.get_node_reaction(osi, nodes[1], o3.cc.DOF2D_X)
    stress = [-force0 - force1]
    v_eff = [stresses[1]]
    h_eff = [stresses[0]]
    d_incs = np.diff(disps, prepend=0)
    for i in range(len(disps)):
        d_inc_i = d_incs[i]
        if target_d_inc < abs(d_inc_i):
            n = int(abs(d_inc_i / target_d_inc))
            d_step = d_inc_i / n
        else:
            n = 1
            d_step = d_inc_i
        for j in range(n):
            o3.integrator.DisplacementControl(osi, nodes[2], o3.cc.DOF2D_X, -d_step)
            o3.Load(osi, nodes[2], [1.0, 0.0])
            o3.Load(osi, nodes[3], [1.0, 0.0])
            o3.analyze(osi, 1)
            o3.gen_reactions(osi)
            stresses = o3.get_ele_response(osi, ele, 'stress')
            print(stresses)
            v_eff.append(stresses[1])
            h_eff.append(stresses[0])
            force0 = o3.get_node_reaction(osi, nodes[0], o3.cc.DOF2D_X)
            force1 = o3.get_node_reaction(osi, nodes[1], o3.cc.DOF2D_X)
            stress.append(-force0 - force1)
            end_strain = o3.get_node_disp(osi, nodes[2], dof=o3.cc.DOF2D_X)
            strain.append(end_strain)

    return -np.array(stress), -np.array(strain), np.array(v_eff), np.array(h_eff), exit_code
def get_inelastic_response(fb,
                           roof_drift_ratio=0.05,
                           elastic=False,
                           w_sfsi=False,
                           out_folder=''):
    """
    Run seismic analysis of a nonlinear FrameBuilding

    Units: Pa, N, m, s

    Parameters
    ----------
    fb: sfsimodels.Frame2DBuilding object
    xi

    Returns
    -------

    """
    osi = o3.OpenSeesInstance(ndm=2, state=3)

    q_floor = 7.0e3  # Pa
    trib_width = fb.floor_length
    trib_mass_per_length = q_floor * trib_width / 9.8

    # Establish nodes and set mass based on trib area
    # Nodes named as: C<column-number>-S<storey-number>, first column starts at C1-S0 = ground level left
    nd = OrderedDict()
    col_xs = np.cumsum(fb.bay_lengths)
    col_xs = np.insert(col_xs, 0, 0)
    n_cols = len(col_xs)
    sto_ys = fb.heights
    sto_ys = np.insert(sto_ys, 0, 0)
    for cc in range(1, n_cols + 1):
        for ss in range(fb.n_storeys + 1):
            nd[f"C{cc}-S{ss}"] = o3.node.Node(osi, col_xs[cc - 1], sto_ys[ss])

            if ss != 0:
                if cc == 1:
                    node_mass = trib_mass_per_length * fb.bay_lengths[0] / 2
                elif cc == n_cols:
                    node_mass = trib_mass_per_length * fb.bay_lengths[-1] / 2
                else:
                    node_mass = trib_mass_per_length * (
                        fb.bay_lengths[cc - 2] + fb.bay_lengths[cc - 1] / 2)
                o3.set_node_mass(osi, nd[f"C{cc}-S{ss}"], node_mass, 0., 0.)

    # Set all nodes on a storey to have the same displacement
    for ss in range(0, fb.n_storeys + 1):
        for cc in range(2, n_cols + 1):
            o3.set_equal_dof(osi, nd[f"C1-S{ss}"], nd[f"C{cc}-S{ss}"], o3.cc.X)

    # Fix all base nodes
    for cc in range(1, n_cols + 1):
        o3.Fix3DOF(osi, nd[f"C{cc}-S0"], o3.cc.FIXED, o3.cc.FIXED, o3.cc.FIXED)

    # Define material
    e_conc = 30.0e9  # kPa
    i_beams = 0.4 * fb.beam_widths * fb.beam_depths**3 / 12
    i_columns = 0.5 * fb.column_widths * fb.column_depths**3 / 12
    a_beams = fb.beam_widths * fb.beam_depths
    a_columns = fb.column_widths * fb.column_depths
    ei_beams = e_conc * i_beams
    ei_columns = e_conc * i_columns
    eps_yield = 300.0e6 / 200e9
    phi_y_col = calc_yield_curvature(fb.column_depths, eps_yield)
    phi_y_beam = calc_yield_curvature(fb.beam_depths, eps_yield)

    # Define beams and columns
    # Columns named as: C<column-number>-S<storey-number>, first column starts at C1-S0 = ground floor left
    # Beams named as: B<bay-number>-S<storey-number>, first beam starts at B1-S1 = first storey left (foundation at S0)

    md = OrderedDict()  # material dict
    sd = OrderedDict()  # section dict
    ed = OrderedDict()  # element dict

    for ss in range(fb.n_storeys):

        # set columns
        lp_i = 0.4
        lp_j = 0.4  # plastic hinge length
        col_transf = o3.geom_transf.Linear2D(
            osi, )  # d_i=[0.0, lp_i], d_j=[0.0, -lp_j]
        for cc in range(1, fb.n_cols + 1):

            ele_str = f"C{cc}-S{ss}S{ss + 1}"

            if elastic:
                top_sect = o3.section.Elastic2D(osi, e_conc,
                                                a_columns[ss][cc - 1],
                                                i_columns[ss][cc - 1])
                bot_sect = o3.section.Elastic2D(osi, e_conc,
                                                a_columns[ss][cc - 1],
                                                i_columns[ss][cc - 1])
            else:
                m_cap = ei_columns[ss][cc - 1] * phi_y_col[ss][cc - 1]
                mat = o3.uniaxial_material.ElasticBilin(
                    osi, ei_columns[ss][cc - 1], 0.05 * ei_columns[ss][cc - 1],
                    1 * phi_y_col[ss][cc - 1])
                mat_axial = o3.uniaxial_material.Elastic(
                    osi, e_conc * a_columns[ss][cc - 1])
                top_sect = o3.section.Aggregator(osi,
                                                 mats=[[mat_axial, o3.cc.P],
                                                       [mat, o3.cc.M_Z]])
                bot_sect = o3.section.Aggregator(osi,
                                                 mats=[[mat_axial, o3.cc.P],
                                                       [mat, o3.cc.M_Z]])

            centre_sect = o3.section.Elastic2D(osi, e_conc,
                                               a_columns[ss][cc - 1],
                                               i_columns[ss][cc - 1])
            sd[ele_str + "T"] = top_sect
            sd[ele_str + "B"] = bot_sect
            sd[ele_str + "C"] = centre_sect

            integ = o3.beam_integration.HingeMidpoint(osi, bot_sect, lp_i,
                                                      top_sect, lp_j,
                                                      centre_sect)

            bot_node = nd[f"C{cc}-S{ss}"]
            top_node = nd[f"C{cc}-S{ss + 1}"]
            ed[ele_str] = o3.element.ForceBeamColumn(osi, [bot_node, top_node],
                                                     col_transf, integ)
            print('mc: ', ei_columns[ss][cc - 1] * phi_y_col[ss][cc - 1])
        # Set beams
        lp_i = 0.4
        lp_j = 0.4
        beam_transf = o3.geom_transf.Linear2D(osi, )
        for bb in range(1, fb.n_bays + 1):

            ele_str = f"C{bb}C{bb + 1}-S{ss + 1}"

            print('mb: ', ei_beams[ss][bb - 1] * phi_y_beam[ss][bb - 1])
            print('phi_b: ', phi_y_beam[ss][bb - 1])

            if elastic:
                left_sect = o3.section.Elastic2D(osi, e_conc,
                                                 a_beams[ss][bb - 1],
                                                 i_beams[ss][bb - 1])
                right_sect = o3.section.Elastic2D(osi, e_conc,
                                                  a_beams[ss][bb - 1],
                                                  i_beams[ss][bb - 1])
            else:
                m_cap = ei_beams[ss][bb - 1] * phi_y_beam[ss][bb - 1]
                # mat_flex = o3.uniaxial_material.ElasticBilin(osi, ei_beams[ss][bb - 1], 0.05 * ei_beams[ss][bb - 1], phi_y_beam[ss][bb - 1])
                mat_flex = o3.uniaxial_material.Steel01(osi,
                                                        m_cap,
                                                        e0=ei_beams[ss][bb -
                                                                        1],
                                                        b=0.05)
                mat_axial = o3.uniaxial_material.Elastic(
                    osi, e_conc * a_beams[ss][bb - 1])
                left_sect = o3.section.Aggregator(osi,
                                                  mats=[[mat_axial, o3.cc.P],
                                                        [mat_flex, o3.cc.M_Z],
                                                        [mat_flex, o3.cc.M_Y]])
                right_sect = o3.section.Aggregator(osi,
                                                   mats=[[mat_axial, o3.cc.P],
                                                         [mat_flex, o3.cc.M_Z],
                                                         [mat_flex,
                                                          o3.cc.M_Y]])

            centre_sect = o3.section.Elastic2D(osi, e_conc,
                                               a_beams[ss][bb - 1],
                                               i_beams[ss][bb - 1])
            integ = o3.beam_integration.HingeMidpoint(osi, left_sect, lp_i,
                                                      right_sect, lp_j,
                                                      centre_sect)

            left_node = nd[f"C{bb}-S{ss + 1}"]
            right_node = nd[f"C{bb + 1}-S{ss + 1}"]
            ed[ele_str] = o3.element.ForceBeamColumn(osi,
                                                     [left_node, right_node],
                                                     beam_transf, integ)

    # Apply gravity loads
    gravity = 9.8 * 1e-2
    # If true then load applied along beam
    g_beams = 0  # TODO: when this is true and analysis is inelastic then failure
    ts_po = o3.time_series.Linear(osi, factor=1)
    o3.pattern.Plain(osi, ts_po)

    for ss in range(1, fb.n_storeys + 1):
        print('ss:', ss)
        if g_beams:
            for bb in range(1, fb.n_bays + 1):
                ele_str = f"C{bb}C{bb + 1}-S{ss}"
                o3.EleLoad2DUniform(osi, ed[ele_str],
                                    -trib_mass_per_length * gravity)
        else:
            for cc in range(1, fb.n_cols + 1):
                if cc == 1 or cc == n_cols:
                    node_mass = trib_mass_per_length * fb.bay_lengths[0] / 2
                elif cc == n_cols:
                    node_mass = trib_mass_per_length * fb.bay_lengths[-1] / 2
                else:
                    node_mass = trib_mass_per_length * (
                        fb.bay_lengths[cc - 2] + fb.bay_lengths[cc - 1] / 2)
                # This works
                o3.Load(osi, nd[f"C{cc}-S{ss}"], [0, -node_mass * gravity, 0])

    tol = 1.0e-3
    o3.constraints.Plain(osi)
    o3.numberer.RCM(osi)
    o3.system.BandGeneral(osi)
    o3.test_check.NormDispIncr(osi, tol, 10)
    o3.algorithm.Newton(osi)
    n_steps_gravity = 10
    d_gravity = 1. / n_steps_gravity
    o3.integrator.LoadControl(osi, d_gravity, num_iter=10)
    o3.analysis.Static(osi)
    o3.analyze(osi, n_steps_gravity)
    o3.gen_reactions(osi)
    print('b1_int: ', o3.get_ele_response(osi, ed['C1C2-S1'], 'force'))
    print('c1_int: ', o3.get_ele_response(osi, ed['C1-S0S1'], 'force'))

    # o3.extensions.to_py_file(osi, 'po.py')
    o3.load_constant(osi, time=0.0)

    # Define the analysis

    # set damping based on first eigen mode
    angular_freq = o3.get_eigen(osi, solver='fullGenLapack', n=1)[0]**0.5
    if isinstance(angular_freq, complex):
        raise ValueError(
            "Angular frequency is complex, issue with stiffness or mass")
    print('angular_freq: ', angular_freq)
    response_period = 2 * np.pi / angular_freq
    print('response period: ', response_period)

    # Run the analysis
    o3r = o3.results.Results2D()
    o3r.cache_path = out_folder
    o3r.dynamic = True
    o3r.pseudo_dt = 0.001  # since analysis is actually static
    o3.set_time(osi, 0.0)
    o3r.start_recorders(osi)
    # o3res.coords = o3.get_all_node_coords(osi)
    # o3res.ele2node_tags = o3.get_all_ele_node_tags_as_dict(osi)
    d_inc = 0.0001

    o3.numberer.RCM(osi)
    o3.system.BandGeneral(osi)
    # o3.test_check.NormUnbalance(osi, 2, max_iter=10, p_flag=2)
    # o3.test_check.FixedNumIter(osi, max_iter=10)
    o3.test_check.NormDispIncr(osi, 0.002, 10, p_flag=0)
    o3.algorithm.Newton(osi)
    o3.integrator.DisplacementControl(osi, nd[f"C1-S{fb.n_storeys}"], o3.cc.X,
                                      d_inc)
    o3.analysis.Static(osi)

    d_max = 0.05 * fb.max_height  # TODO: set to 5%
    print('d_max: ', d_max)
    # n_steps = int(d_max / d_inc)

    print("Analysis starting")
    print('int_disp: ',
          o3.get_node_disp(osi, nd[f"C1-S{fb.n_storeys}"], o3.cc.X))
    # opy.recorder('Element', '-file', 'ele_out.txt', '-time', '-ele', 1, 'force')
    tt = 0
    outputs = {
        'h_disp': [],
        'vb': [],
        'REACT-C1C2-S1': [],
        'REACT-C1-S0S1': [],
    }
    hd = 0
    n_max = 2
    n_cycs = 0
    xs = fb.heights / fb.max_height  # TODO: more sophisticated displacement profile
    ts_po = o3.time_series.Linear(osi, factor=1)
    o3.pattern.Plain(osi, ts_po)
    for i, xp in enumerate(xs):
        o3.Load(osi, nd[f"C1-S{i + 1}"], [xp, 0.0, 0])

    # o3.analyze(osi, 2)
    # n_max = 0
    time = [0]
    while n_cycs < n_max:
        print('n_cycles: ', n_cycs)
        for i in range(2):
            if i == 0:
                o3.integrator.DisplacementControl(osi,
                                                  nd[f"C1-S{fb.n_storeys}"],
                                                  o3.cc.X, d_inc)
            else:
                o3.integrator.DisplacementControl(osi,
                                                  nd[f"C1-S{fb.n_storeys}"],
                                                  o3.cc.X, -d_inc)
            while hd * (-1)**i < d_max:
                ok = o3.analyze(osi, 10)
                time.append(time[len(time) - 1] + 1)
                hd = o3.get_node_disp(osi, nd[f"C1-S{fb.n_storeys}"], o3.cc.X)
                outputs['h_disp'].append(hd)
                o3.gen_reactions(osi)
                vb = 0
                for cc in range(1, fb.n_cols + 1):
                    vb += o3.get_node_reaction(osi, nd[f"C{cc}-S0"], o3.cc.X)
                outputs['vb'].append(-vb)
                outputs['REACT-C1C2-S1'].append(
                    o3.get_ele_response(osi, ed['C1C2-S1'], 'force'))
                outputs['REACT-C1-S0S1'].append(
                    o3.get_ele_response(osi, ed['C1-S0S1'], 'force'))

        n_cycs += 1

    o3.wipe(osi)
    o3r.save_to_cache()
    for item in outputs:
        outputs[item] = np.array(outputs[item])
    print('complete')
    return outputs
def gen_response(period, xi, asig, etype, fos_for_dt=None):

    # Define inelastic SDOF
    mass = 1.0
    f_yield = 1.5  # Reduce this to make it nonlinear
    r_post = 0.0

    # Initialise OpenSees instance
    osi = o3.OpenSeesInstance(ndm=2, state=0)

    # Establish nodes
    bot_node = o3.node.Node(osi, 0, 0)
    top_node = o3.node.Node(osi, 0, 0)

    # Fix bottom node
    o3.Fix3DOF(osi, top_node, o3.cc.FREE, o3.cc.FIXED, o3.cc.FIXED)
    o3.Fix3DOF(osi, bot_node, o3.cc.FIXED, o3.cc.FIXED, o3.cc.FIXED)
    # Set out-of-plane DOFs to be slaved
    o3.EqualDOF(osi, top_node, bot_node, [o3.cc.Y, o3.cc.ROTZ])

    # nodal mass (weight / g):
    o3.Mass(osi, top_node, mass, 0., 0.)

    # Define material
    k_spring = 4 * np.pi**2 * mass / period**2
    # bilinear_mat = o3.uniaxial_material.Steel01(osi, fy=f_yield, e0=k_spring, b=r_post)
    mat = o3.uniaxial_material.Elastic(osi, e_mod=k_spring)

    # Assign zero length element, # Note: pass actual node and material objects into element
    o3.element.ZeroLength(osi, [bot_node, top_node],
                          mats=[mat],
                          dirs=[o3.cc.DOF2D_X],
                          r_flag=1)

    # Define the dynamic analysis

    # Define the dynamic analysis
    acc_series = o3.time_series.Path(osi, dt=asig.dt, values=-1 *
                                     asig.values)  # should be negative
    o3.pattern.UniformExcitation(osi, dir=o3.cc.X, accel_series=acc_series)

    # set damping based on first eigen mode
    angular_freq = o3.get_eigen(osi, solver='fullGenLapack', n=1)[0]**0.5
    period = 2 * np.pi / angular_freq
    beta_k = 2 * xi / angular_freq
    o3.rayleigh.Rayleigh(osi,
                         alpha_m=0.0,
                         beta_k=beta_k,
                         beta_k_init=0.0,
                         beta_k_comm=0.0)

    o3.set_time(osi, 0.0)
    # Run the dynamic analysis
    o3.wipe_analysis(osi)

    # Run the dynamic analysis
    o3.numberer.RCM(osi)
    o3.system.FullGeneral(osi)
    if etype == 'central_difference':
        o3.algorithm.Linear(osi, factor_once=True)
        o3.integrator.CentralDifference(osi)
        explicit_dt = 2 / angular_freq / fos_for_dt
        analysis_dt = explicit_dt
    elif etype == 'implicit':
        o3.algorithm.Newton(osi)
        o3.integrator.Newmark(osi, gamma=0.5, beta=0.25)
        analysis_dt = 0.001
    else:
        raise ValueError()
    o3.constraints.Transformation(osi)
    o3.analysis.Transient(osi)

    o3.test_check.EnergyIncr(osi, tol=1.0e-10, max_iter=10)
    analysis_time = asig.time[-1]

    outputs = {
        "time": [],
        "rel_disp": [],
        "rel_accel": [],
        "rel_vel": [],
        "force": []
    }
    rec_dt = 0.002
    n_incs = int(analysis_dt / rec_dt)
    n_incs = 1
    while o3.get_time(osi) < analysis_time:
        o3.analyze(osi, n_incs, analysis_dt)
        curr_time = o3.get_time(osi)
        outputs["time"].append(curr_time)
        outputs["rel_disp"].append(o3.get_node_disp(osi, top_node, o3.cc.X))
        outputs["rel_vel"].append(o3.get_node_vel(osi, top_node, o3.cc.X))
        outputs["rel_accel"].append(o3.get_node_accel(osi, top_node, o3.cc.X))
        o3.gen_reactions(osi)
        outputs["force"].append(-o3.get_node_reaction(
            osi, bot_node, o3.cc.X))  # Negative since diff node
    o3.wipe(osi)
    for item in outputs:
        outputs[item] = np.array(outputs[item])
    return outputs