Esempio n. 1
0
def run():
    osi = o3.OpenSeesInstance(ndm=1, ndf=1)
    mat = o3.uniaxial_material.Elastic(osi, 3000.0)

    n1 = o3.node.Node(osi, 0.0)
    n2 = o3.node.Node(osi, 72.0)

    o3.Fix1DOF(osi, n1, o3.cc.FIXED)

    o3.element.Truss(osi, [n1, n2], 10.0, mat)
    ts0 = o3.time_series.Linear(osi)
    o3.pattern.Plain(osi, ts0)
    o3.Load(osi, n1, [10])

    o3.constraints.Transformation(osi)
    o3.test_check.NormDispIncr(osi, tol=1.0e-6, max_iter=10)
    o3.numberer.RCM(osi)
    o3.system.ProfileSPD(osi)
    o3.algorithm.NewtonLineSearch(osi, 0.75)
    o3.integrator.Newmark(osi, 0.5, 0.25)

    o3.analysis.VariableTransient(osi)

    o3.analyze(osi, 5, 0.0001, 0.00001, 0.001, 10)
    time = o3.get_time(osi)
    print(f'time: ', o3.get_time(osi))
    approx_vtime = 0.0001 + 0.001  # One step at target, then one step at maximum
    assert 0.99 < time / approx_vtime < 1.01, (time, approx_vtime)
    o3.set_time(osi, 0.0)
    # Can still run a non-variable analysis - since analyze function has multiple dispatch.
    o3.analyze(osi, 5, 0.0001)
    time = o3.get_time(osi)
    print(f'time: ', o3.get_time(osi))
    approx_vtime = 0.0001 * 5  # variable transient is not active so time should be dt * 5
    # If variable transient is not active then time would be 0.0005
    assert 0.99 < time / approx_vtime < 1.01, (time, approx_vtime)
Esempio n. 2
0
def site_response(sp,
                  asig,
                  freqs=(0.5, 10),
                  xi=0.03,
                  analysis_dt=0.001,
                  dy=0.5,
                  analysis_time=None,
                  outs=None,
                  rec_dt=None):
    """
    Run seismic analysis of a soil profile - example based on:
    http://opensees.berkeley.edu/wiki/index.php/Site_Response_Analysis_of_a_Layered_Soil_Column_(Total_Stress_Analysis)

    Parameters
    ----------
    sp: sfsimodels.SoilProfile object
        A soil profile
    asig: eqsig.AccSignal object
        An acceleration signal

    Returns
    -------

    """
    if analysis_time is None:
        analysis_time = asig.time[-1]
    if outs is None:
        outs = {
            'ACCX': [0]
        }  # Export the horizontal acceleration at the surface
    if rec_dt is None:
        rec_dt = analysis_dt

    osi = o3.OpenSeesInstance(ndm=2, ndf=2, state=3)
    assert isinstance(sp, sm.SoilProfile)
    sp.gen_split(props=['shear_vel', 'unit_mass'], target=dy)
    thicknesses = sp.split["thickness"]
    n_node_rows = len(thicknesses) + 1
    node_depths = np.cumsum(sp.split["thickness"])
    node_depths = np.insert(node_depths, 0, 0)
    ele_depths = (node_depths[1:] + node_depths[:-1]) / 2
    unit_masses = sp.split["unit_mass"] / 1e3

    grav = 9.81
    omega_1 = 2 * np.pi * freqs[0]
    omega_2 = 2 * np.pi * freqs[1]
    a0 = 2 * xi * omega_1 * omega_2 / (omega_1 + omega_2)
    a1 = 2 * xi / (omega_1 + omega_2)

    k0 = 0.5
    pois = k0 / (1 + k0)

    newmark_gamma = 0.5
    newmark_beta = 0.25

    ele_width = min(thicknesses)
    total_soil_nodes = len(thicknesses) * 2 + 2

    # Define nodes and set boundary conditions for simple shear deformation
    # Start at top and build down?
    sn = [[o3.node.Node(osi, 0, 0), o3.node.Node(osi, ele_width, 0)]]
    for i in range(1, n_node_rows):
        # Establish left and right nodes
        sn.append([
            o3.node.Node(osi, 0, -node_depths[i]),
            o3.node.Node(osi, ele_width, -node_depths[i])
        ])
        # set x and y dofs equal for left and right nodes
        o3.EqualDOF(osi, sn[i][0], sn[i][1], [o3.cc.X, o3.cc.Y])
    sn = np.array(sn)
    # Fix base nodes
    o3.Fix2DOF(osi, sn[-1][0], o3.cc.FREE, o3.cc.FIXED)
    o3.Fix2DOF(osi, sn[-1][1], o3.cc.FREE, o3.cc.FIXED)

    # Define dashpot nodes
    dashpot_node_l = o3.node.Node(osi, 0, -node_depths[-1])
    dashpot_node_2 = o3.node.Node(osi, 0, -node_depths[-1])
    o3.Fix2DOF(osi, dashpot_node_l, o3.cc.FIXED, o3.cc.FIXED)
    o3.Fix2DOF(osi, dashpot_node_2, o3.cc.FREE, o3.cc.FIXED)

    # define equal DOF for dashpot and soil base nodes
    o3.EqualDOF(osi, sn[-1][0], sn[-1][1], [o3.cc.X])
    o3.EqualDOF(osi, sn[-1][0], dashpot_node_2, [o3.cc.X])

    # define materials
    ele_thick = 1.0  # m
    soil_mats = []
    strains = np.logspace(-6, -0.5, 16)
    ref_strain = 0.005
    rats = 1. / (1 + (strains / ref_strain)**0.91)
    prev_args = []
    prev_kwargs = {}
    prev_sl_type = None
    eles = []
    tau_inds = []
    total_stress_inds = 0
    strs_inds = []
    total_strain_inds = 0
    for i in range(len(thicknesses)):
        y_depth = ele_depths[i]

        sl_id = sp.get_layer_index_by_depth(y_depth)
        sl = sp.layer(sl_id)
        app2mod = {}
        if y_depth > sp.gwl:
            umass = sl.unit_sat_mass / 1e3
        else:
            umass = sl.unit_dry_mass / 1e3
        # Define material
        if sl.type == 'pm4sand':
            sl_class = o3.nd_material.PM4Sand
            overrides = {'nu': pois, 'p_atm': 101, 'unit_moist_mass': umass}
            app2mod = sl.app2mod
        elif sl.type == 'sdmodel':
            sl_class = o3.nd_material.StressDensity
            overrides = {'nu': pois, 'p_atm': 101, 'unit_moist_mass': umass}
            app2mod = sl.app2mod
        elif sl.type == 'pimy':
            sl_class = o3.nd_material.PressureIndependMultiYield
            overrides = {
                'nu': pois,
                'p_atm': 101,
                'rho': umass,
                'nd': 2.0,
                'g_mod_ref': sl.g_mod / 1e3,
                'bulk_mod_ref': sl.bulk_mod / 1e3,
                'cohesion': sl.cohesion / 1e3,
                'd': 0.0,
                # 'no_yield_surf': 20
            }
            tau_inds.append(total_stress_inds + 3)  # 4th
            total_stress_inds += 5
            strs_inds.append(total_strain_inds + 2)  # 3rd
            total_strain_inds += 3
        else:
            sl_class = o3.nd_material.ElasticIsotropic
            sl.e_mod = 2 * sl.g_mod * (1 - sl.poissons_ratio) / 1e3
            app2mod['rho'] = 'unit_moist_mass'
            overrides = {'nu': sl.poissons_ratio, 'unit_moist_mass': umass}

        # opw.extensions.to_py_file(osi)
        args, kwargs = o3.extensions.get_o3_kwargs_from_obj(
            sl, sl_class, custom=app2mod, overrides=overrides)
        changed = 0
        if sl.type != prev_sl_type or len(args) != len(prev_args) or len(
                kwargs) != len(prev_kwargs):
            changed = 1
        else:
            for j, arg in enumerate(args):
                if not np.isclose(arg, prev_args[j]):
                    changed = 1
            for pm in kwargs:
                if pm not in prev_kwargs or not np.isclose(
                        kwargs[pm], prev_kwargs[pm]):
                    changed = 1

        if changed:
            mat = sl_class(osi, *args, **kwargs)
            prev_sl_type = sl.type
            prev_args = copy.deepcopy(args)
            prev_kwargs = copy.deepcopy(kwargs)

            soil_mats.append(mat)

        # def element
        nodes = [sn[i + 1][0], sn[i + 1][1], sn[i][1],
                 sn[i][0]]  # anti-clockwise
        # ele = o3.element.Quad(osi, nodes, ele_thick, o3.cc.PLANE_STRAIN, mat, b2=grav * unit_masses[i])
        ele = o3.element.SSPquad(osi,
                                 nodes,
                                 mat,
                                 'PlaneStrain',
                                 ele_thick,
                                 0.0,
                                 b2=grav * unit_masses[i])
        eles.append(ele)

    # define material and element for viscous dampers
    base_sl = sp.layer(sp.n_layers)
    c_base = ele_width * base_sl.unit_dry_mass / 1e3 * sp.get_shear_vel_at_depth(
        sp.height)
    dashpot_mat = o3.uniaxial_material.Viscous(osi, c_base, alpha=1.)
    o3.element.ZeroLength(osi, [dashpot_node_l, dashpot_node_2],
                          mats=[dashpot_mat],
                          dirs=[o3.cc.DOF2D_X])

    # Static analysis
    o3.constraints.Transformation(osi)
    o3.test_check.NormDispIncr(osi, tol=1.0e-4, max_iter=30, p_flag=0)
    o3.algorithm.Newton(osi)
    o3.numberer.RCM(osi)
    o3.system.ProfileSPD(osi)
    o3.integrator.Newmark(osi, newmark_gamma, newmark_beta)
    o3.analysis.Transient(osi)
    o3.analyze(osi, 40, 1.)

    for i in range(len(soil_mats)):
        if isinstance(soil_mats[i], o3.nd_material.PM4Sand) or isinstance(
                soil_mats[i], o3.nd_material.PressureIndependMultiYield):
            o3.update_material_stage(osi, soil_mats[i], 1)
    o3.analyze(osi, 50, 0.5)

    # reset time and analysis
    o3.set_time(osi, 0.0)
    o3.wipe_analysis(osi)

    # o3.recorder.NodeToFile(osi, 'sample_out.txt', node=nd["R0L"], dofs=[o3.cc.X], res_type='accel')
    na = o3.recorder.NodeToArrayCache(osi,
                                      node=sn[0][0],
                                      dofs=[o3.cc.X],
                                      res_type='accel')
    otypes = ['ACCX', 'TAU', 'STRS']
    ods = {}
    for otype in outs:
        if otype == 'ACCX':

            ods['ACCX'] = []
            if isinstance(outs['ACCX'], str) and outs['ACCX'] == 'all':
                ods['ACCX'] = o3.recorder.NodesToArrayCache(osi,
                                                            nodes=sn[:, 0],
                                                            dofs=[o3.cc.X],
                                                            res_type='accel',
                                                            dt=rec_dt)
            else:
                for i in range(len(outs['ACCX'])):
                    ind = np.argmin(abs(node_depths - outs['ACCX'][i]))
                    ods['ACCX'].append(
                        o3.recorder.NodeToArrayCache(osi,
                                                     node=sn[ind][0],
                                                     dofs=[o3.cc.X],
                                                     res_type='accel',
                                                     dt=rec_dt))
        if otype == 'TAU':
            ods['TAU'] = []
            if isinstance(outs['TAU'], str) and outs['TAU'] == 'all':
                ods['TAU'] = o3.recorder.ElementsToArrayCache(
                    osi, eles=eles, arg_vals=['stress'], dt=rec_dt)
            else:
                for i in range(len(outs['TAU'])):
                    ind = np.argmin(abs(ele_depths - outs['TAU'][i]))
                    ods['TAU'].append(
                        o3.recorder.ElementToArrayCache(osi,
                                                        ele=eles[ind],
                                                        arg_vals=['stress'],
                                                        dt=rec_dt))

        if otype == 'STRS':
            ods['STRS'] = []
            if isinstance(outs['STRS'], str) and outs['STRS'] == 'all':
                ods['STRS'] = o3.recorder.ElementsToArrayCache(
                    osi, eles=eles, arg_vals=['strain'], dt=rec_dt)
            else:
                for i in range(len(outs['STRS'])):
                    ind = np.argmin(abs(ele_depths - outs['STRS'][i]))
                    ods['STRS'].append(
                        o3.recorder.ElementToArrayCache(osi,
                                                        ele=eles[ind],
                                                        arg_vals=['strain'],
                                                        dt=rec_dt))

    # Define the dynamic analysis
    ts_obj = o3.time_series.Path(osi,
                                 dt=asig.dt,
                                 values=asig.velocity * -1,
                                 factor=c_base)
    o3.pattern.Plain(osi, ts_obj)
    o3.Load(osi, sn[-1][0], [1., 0.])

    # Run the dynamic analysis
    o3.algorithm.Newton(osi)
    o3.system.SparseGeneral(osi)
    o3.numberer.RCM(osi)
    o3.constraints.Transformation(osi)
    o3.integrator.Newmark(osi, newmark_gamma, newmark_beta)
    o3.rayleigh.Rayleigh(osi, a0, a1, 0, 0)
    o3.analysis.Transient(osi)

    o3.test_check.EnergyIncr(osi, tol=1.0e-7, max_iter=10)
    o3.extensions.to_py_file(osi)

    while opy.getTime() < analysis_time:
        print(opy.getTime())
        if o3.analyze(osi, 1, analysis_dt):
            print('failed')
            break
    opy.wipe()
    out_dict = {}
    for otype in ods:
        if isinstance(ods[otype], list):
            out_dict[otype] = []
            for i in range(len(ods[otype])):
                a = ods[otype][i].collect()
                out_dict[otype].append(
                    a[3::4])  # TODO: not working for generic case
            out_dict[otype] = np.array(out_dict[otype])
        else:
            if otype == 'TAU':
                a = ods[otype].collect()
                out_dict[otype] = a[:, tau_inds].T * 1e3
            elif otype == 'STRS':
                a = ods[otype].collect()
                out_dict[otype] = a[:, strs_inds].T
            else:
                out_dict[otype] = ods[otype].collect().T
    out_dict['time'] = np.arange(0, len(out_dict[otype][0])) * rec_dt

    return out_dict
Esempio n. 3
0
def site_response(sp,
                  asig,
                  freqs=(0.5, 10),
                  xi=0.03,
                  analysis_dt=0.001,
                  dy=0.5,
                  analysis_time=None,
                  outs=None,
                  rec_dt=None,
                  use_explicit=0):
    """
    Run seismic analysis of a soil profile that has a compliant base

    Parameters
    ----------
    sp: sfsimodels.SoilProfile object
        A soil profile
    asig: eqsig.AccSignal object
        An acceleration signal

    Returns
    -------

    """
    if analysis_time is None:
        analysis_time = asig.time[-1]
    if outs is None:
        outs = {
            'ACCX': [0]
        }  # Export the horizontal acceleration at the surface
    if rec_dt is None:
        rec_dt = analysis_dt

    osi = o3.OpenSeesInstance(ndm=2, ndf=2, state=3)
    assert isinstance(sp, sm.SoilProfile)
    sp.gen_split(props=['shear_vel', 'unit_mass'], target=dy)
    thicknesses = sp.split["thickness"]
    n_node_rows = len(thicknesses) + 1
    node_depths = np.cumsum(sp.split["thickness"])
    node_depths = np.insert(node_depths, 0, 0)
    ele_depths = (node_depths[1:] + node_depths[:-1]) / 2
    unit_masses = sp.split["unit_mass"] / 1e3

    grav = 9.81
    omega_1 = 2 * np.pi * freqs[0]
    omega_2 = 2 * np.pi * freqs[1]
    a0 = 2 * xi * omega_1 * omega_2 / (omega_1 + omega_2)
    a1 = 2 * xi / (omega_1 + omega_2)

    k0 = 0.5
    pois = k0 / (1 + k0)

    newmark_gamma = 0.5
    newmark_beta = 0.25

    ele_width = min(thicknesses)

    # Define nodes and set boundary conditions for simple shear deformation
    # Start at top and build down?
    sn = [[o3.node.Node(osi, 0, 0), o3.node.Node(osi, ele_width, 0)]]
    for i in range(1, n_node_rows):
        # Establish left and right nodes
        sn.append([
            o3.node.Node(osi, 0, -node_depths[i]),
            o3.node.Node(osi, ele_width, -node_depths[i])
        ])
        # set x and y dofs equal for left and right nodes
        o3.EqualDOF(osi, sn[i][0], sn[i][1], [o3.cc.X, o3.cc.Y])

    # Fix base nodes
    o3.Fix2DOF(osi, sn[-1][0], o3.cc.FREE, o3.cc.FIXED)
    o3.Fix2DOF(osi, sn[-1][1], o3.cc.FREE, o3.cc.FIXED)

    # Define dashpot nodes
    dashpot_node_l = o3.node.Node(osi, 0, -node_depths[-1])
    dashpot_node_2 = o3.node.Node(osi, 0, -node_depths[-1])
    o3.Fix2DOF(osi, dashpot_node_l, o3.cc.FIXED, o3.cc.FIXED)
    o3.Fix2DOF(osi, dashpot_node_2, o3.cc.FREE, o3.cc.FIXED)

    # define equal DOF for dashpot and soil base nodes
    o3.EqualDOF(osi, sn[-1][0], sn[-1][1], [o3.cc.X])
    o3.EqualDOF(osi, sn[-1][0], dashpot_node_2, [o3.cc.X])

    # define materials
    ele_thick = 1.0  # m
    soil_mats = []
    prev_args = []
    prev_kwargs = {}
    prev_sl_type = None
    eles = []
    for i in range(len(thicknesses)):
        y_depth = ele_depths[i]

        sl_id = sp.get_layer_index_by_depth(y_depth)
        sl = sp.layer(sl_id)
        app2mod = {}
        if y_depth > sp.gwl:
            umass = sl.unit_sat_mass / 1e3
        else:
            umass = sl.unit_dry_mass / 1e3
        # Define material
        sl_class = o3.nd_material.ElasticIsotropic
        sl.e_mod = 2 * sl.g_mod * (1 + sl.poissons_ratio) / 1e3
        app2mod['rho'] = 'unit_moist_mass'
        overrides = {'nu': sl.poissons_ratio, 'unit_moist_mass': umass}

        args, kwargs = o3.extensions.get_o3_kwargs_from_obj(
            sl, sl_class, custom=app2mod, overrides=overrides)
        changed = 0
        if sl.type != prev_sl_type or len(args) != len(prev_args) or len(
                kwargs) != len(prev_kwargs):
            changed = 1
        else:
            for j, arg in enumerate(args):
                if not np.isclose(arg, prev_args[j]):
                    changed = 1
            for pm in kwargs:
                if pm not in prev_kwargs or not np.isclose(
                        kwargs[pm], prev_kwargs[pm]):
                    changed = 1

        if changed:
            mat = sl_class(osi, *args, **kwargs)
            prev_sl_type = sl.type
            prev_args = copy.deepcopy(args)
            prev_kwargs = copy.deepcopy(kwargs)

            soil_mats.append(mat)

        # def element
        nodes = [sn[i + 1][0], sn[i + 1][1], sn[i][1],
                 sn[i][0]]  # anti-clockwise
        eles.append(
            o3.element.SSPquad(osi, nodes, mat, o3.cc.PLANE_STRAIN, ele_thick,
                               0.0, -grav))
        # eles.append(o3.element.Quad(osi, nodes, mat=mat, otype=o3.cc.PLANE_STRAIN, thick=ele_thick, pressure=0.0, rho=unit_masses[i], b2=grav))

    # define material and element for viscous dampers
    base_sl = sp.layer(sp.n_layers)
    c_base = ele_width * base_sl.unit_dry_mass / 1e3 * sp.get_shear_vel_at_depth(
        sp.height)
    dashpot_mat = o3.uniaxial_material.Viscous(osi, c_base, alpha=1.)
    o3.element.ZeroLength(osi, [dashpot_node_l, dashpot_node_2],
                          mats=[dashpot_mat],
                          dirs=[o3.cc.DOF2D_X])

    # Static analysis
    o3.constraints.Transformation(osi)
    o3.test_check.NormDispIncr(osi, tol=1.0e-4, max_iter=30, p_flag=0)
    o3.algorithm.Newton(osi)
    o3.numberer.RCM(osi)
    o3.system.ProfileSPD(osi)
    o3.integrator.Newmark(osi, newmark_gamma, newmark_beta)
    o3.analysis.Transient(osi)
    o3.analyze(osi, 40, 1.)

    for i in range(len(soil_mats)):
        if isinstance(soil_mats[i], o3.nd_material.PM4Sand) or isinstance(
                soil_mats[i], o3.nd_material.PressureIndependMultiYield):
            o3.update_material_stage(osi, soil_mats[i], 1)
    o3.analyze(osi, 50, 0.5)

    # reset time and analysis
    o3.set_time(osi, 0.0)
    o3.wipe_analysis(osi)

    ods = {}
    for otype in outs:
        if otype == 'ACCX':

            ods['ACCX'] = []
            if isinstance(outs['ACCX'], str) and outs['ACCX'] == 'all':
                ods['ACCX'] = o3.recorder.NodesToArrayCache(osi,
                                                            nodes=sn[:][0],
                                                            dofs=[o3.cc.X],
                                                            res_type='accel',
                                                            dt=rec_dt)
            else:
                for i in range(len(outs['ACCX'])):
                    ind = np.argmin(abs(node_depths - outs['ACCX'][i]))
                    ods['ACCX'].append(
                        o3.recorder.NodeToArrayCache(osi,
                                                     node=sn[ind][0],
                                                     dofs=[o3.cc.X],
                                                     res_type='accel',
                                                     dt=rec_dt))
        if otype == 'TAU':
            ods['TAU'] = []
            if isinstance(outs['TAU'], str) and outs['TAU'] == 'all':
                ods['TAU'] = o3.recorder.ElementsToArrayCache(
                    osi, eles=eles, arg_vals=['stress'], dt=rec_dt)
            else:
                for i in range(len(outs['TAU'])):
                    ind = np.argmin(abs(ele_depths - outs['TAU'][i]))
                    ods['TAU'].append(
                        o3.recorder.ElementToArrayCache(osi,
                                                        ele=eles[ind],
                                                        arg_vals=['stress'],
                                                        dt=rec_dt))

        if otype == 'STRS':
            ods['STRS'] = []
            if isinstance(outs['STRS'], str) and outs['STRS'] == 'all':
                ods['STRS'] = o3.recorder.ElementsToArrayCache(
                    osi, eles=eles, arg_vals=['strain'], dt=rec_dt)
            else:
                for i in range(len(outs['STRS'])):
                    ind = np.argmin(abs(ele_depths - outs['STRS'][i]))
                    ods['STRS'].append(
                        o3.recorder.ElementToArrayCache(osi,
                                                        ele=eles[ind],
                                                        arg_vals=['strain'],
                                                        dt=rec_dt))

    # Define the dynamic analysis
    ts_obj = o3.time_series.Path(osi,
                                 dt=asig.dt,
                                 values=asig.velocity * 1,
                                 factor=c_base)
    o3.pattern.Plain(osi, ts_obj)
    o3.Load(osi, sn[-1][0], [1., 0.])

    # Run the dynamic analysis
    if use_explicit:
        o3.system.FullGeneral(osi)
        # o3.algorithm.Newton(osi)
        o3.algorithm.Linear(osi)
        # o3.integrator.ExplicitDifference(osi)
        # o3.integrator.CentralDifference(osi)
        o3.integrator.NewmarkExplicit(osi, newmark_gamma)  # also works
    else:
        o3.system.SparseGeneral(osi)
        o3.algorithm.Newton(osi)
        o3.integrator.Newmark(osi, newmark_gamma, newmark_beta)
    o3.numberer.RCM(osi)
    o3.constraints.Transformation(osi)
    n = 2
    modal_damp = 0
    omegas = np.array(o3.get_eigen(osi, n=n))**0.5
    response_periods = 2 * np.pi / omegas
    print('response_periods: ', response_periods)
    if not modal_damp:
        o3.rayleigh.Rayleigh(osi, a0, a1, 0, 0)
    else:
        o3.ModalDamping(osi, [xi, xi])
    o3.analysis.Transient(osi)

    o3.test_check.NormDispIncr(osi, tol=1.0e-6, max_iter=10)

    while o3.get_time(osi) < analysis_time:
        print(o3.get_time(osi))
        if o3.analyze(osi, 1, analysis_dt):
            print('failed')
            break
    o3.wipe(osi)
    out_dict = {}
    for otype in ods:
        if isinstance(ods[otype], list):
            out_dict[otype] = []
            for i in range(len(ods[otype])):
                out_dict[otype].append(ods[otype][i].collect())
            out_dict[otype] = np.array(out_dict[otype])
        else:
            out_dict[otype] = ods[otype].collect().T
    out_dict['time'] = np.arange(0, analysis_time, rec_dt)

    return out_dict
Esempio n. 4
0
def site_response(sp, asig, linear=0):
    """
    Run seismic analysis of a soil profile - example based on:
    http://opensees.berkeley.edu/wiki/index.php/Site_Response_Analysis_of_a_Layered_Soil_Column_(Total_Stress_Analysis)

    :param sp: sfsimodels.SoilProfile object
        A soil profile
    :param asig: eqsig.AccSignal object
        An acceleration signal
    :return:
    """

    osi = o3.OpenSeesInstance(ndm=2, ndf=2, state=3)
    assert isinstance(sp, sm.SoilProfile)
    sp.gen_split(props=['shear_vel', 'unit_mass', 'cohesion', 'phi', 'bulk_mod', 'poissons_ratio', 'strain_peak'])
    thicknesses = sp.split["thickness"]
    n_node_rows = len(thicknesses) + 1
    node_depths = np.cumsum(sp.split["thickness"])
    node_depths = np.insert(node_depths, 0, 0)
    ele_depths = (node_depths[1:] + node_depths[:-1]) / 2
    shear_vels = sp.split["shear_vel"]
    unit_masses = sp.split["unit_mass"] / 1e3
    g_mods = unit_masses * shear_vels ** 2
    poissons_ratio = sp.split['poissons_ratio']
    youngs_mods = 2 * g_mods * (1 - poissons_ratio)
    bulk_mods = youngs_mods / (3 * (1 - 2 * poissons_ratio))
    bulk_mods = sp.split['bulk_mod'] / 1e3

    ref_pressure = 80.0
    cohesions = sp.split['cohesion'] / 1e3
    phis = sp.split['phi']
    strain_peaks = sp.split['strain_peak']
    grav = 9.81
    damping = 0.03
    omega_1 = 2 * np.pi * 0.5
    omega_2 = 2 * np.pi * 10
    a0 = 2 * damping * omega_1 * omega_2 / (omega_1 + omega_2)
    a1 = 2 * damping / (omega_1 + omega_2)

    newmark_gamma = 0.5
    newmark_beta = 0.25

    ele_width = min(thicknesses)
    total_soil_nodes = len(thicknesses) * 2 + 2

    # Define nodes and set boundary conditions for simple shear deformation
    # Start at top and build down?
    nd = OrderedDict()
    nd["R0L"] = o3.node.Node(osi, 0, 0)  # row 0 left
    nd["R0R"] = o3.node.Node(osi, ele_width, 0)
    for i in range(1, n_node_rows):
        # Establish left and right nodes
        nd[f"R{i}L"] = o3.node.Node(osi, 0, -node_depths[i])
        nd[f"R{i}R"] = o3.node.Node(osi, ele_width, -node_depths[i])
        # set x and y dofs equal for left and right nodes
        o3.EqualDOF(osi, nd[f"R{i}L"], nd[f"R{i}R"], [o3.cc.X, o3.cc.Y])

    # Fix base nodes
    o3.Fix2DOF(osi, nd[f"R{n_node_rows - 1}L"], o3.cc.FREE, o3.cc.FIXED)
    o3.Fix2DOF(osi, nd[f"R{n_node_rows - 1}R"], o3.cc.FREE, o3.cc.FIXED)

    # Define dashpot nodes
    dashpot_node_l = o3.node.Node(osi, 0, -node_depths[-1])
    dashpot_node_2 = o3.node.Node(osi, 0, -node_depths[-1])
    o3.Fix2DOF(osi, dashpot_node_l,  o3.cc.FIXED, o3.cc.FIXED)
    o3.Fix2DOF(osi, dashpot_node_2, o3.cc.FREE, o3.cc.FIXED)

    # define equal DOF for dashpot and soil base nodes
    o3.EqualDOF(osi, nd[f"R{n_node_rows - 1}L"], nd[f"R{n_node_rows - 1}R"], [o3.cc.X])
    o3.EqualDOF(osi, nd[f"R{n_node_rows - 1}L"], dashpot_node_2, [o3.cc.X])

    # define materials
    ele_thick = 1.0  # m
    soil_mats = []
    strains = np.logspace(-6, -0.5, 16)
    ref_strain = 0.005
    rats = 1. / (1 + (strains / ref_strain) ** 0.91)
    eles = []
    for i in range(len(thicknesses)):
        if not linear:
            mat = o3.nd_material.PressureIndependMultiYield(osi, 2, unit_masses[i], g_mods[i],
                                                         bulk_mods[i], cohesions[i], strain_peaks[i],
                                                         phis[i], d=0.0, n_surf=16,
                                                         strains=strains, ratios=rats)
        else:
            mat = o3.nd_material.ElasticIsotropic(osi, youngs_mods[i], poissons_ratio[i], rho=unit_masses[i])
        soil_mats.append(mat)

        # def element
        nodes = [nd[f"R{i + 1}L"], nd[f"R{i + 1}R"], nd[f"R{i}R"], nd[f"R{i}L"]]
        ele = o3.element.Quad(osi, nodes, ele_thick, o3.cc.PLANE_STRAIN, mat, b2=grav * unit_masses[i])
        eles.append(ele)

    # define material and element for viscous dampers
    c_base = ele_width * unit_masses[-1] * shear_vels[-1]
    dashpot_mat = o3.uniaxial_material.Viscous(osi, c_base, alpha=1.)
    o3.element.ZeroLength(osi, [dashpot_node_l, dashpot_node_2], mats=[dashpot_mat], dirs=[o3.cc.DOF2D_X])

    # Static analysis
    o3.constraints.Transformation(osi)
    o3.test_check.NormDispIncr(osi, tol=1.0e-4, max_iter=30, p_flag=0)
    o3.algorithm.Newton(osi)
    o3.numberer.RCM(osi)
    o3.system.ProfileSPD(osi)
    o3.integrator.Newmark(osi, newmark_gamma, newmark_beta)
    o3.analysis.Transient(osi)
    o3.analyze(osi, 40, 1.)

    # for i in range(len(soil_mats)):
    #     o3.update_material_stage(osi, soil_mats[i], 1)
    o3.analyze(osi, 50, 0.5)

    # reset time and analysis
    o3.set_time(osi, 0.0)
    o3.wipe_analysis(osi)

    na = o3.recorder.NodeToArrayCache(osi, node=nd["R0L"], dofs=[o3.cc.X], res_type='accel')
    es = o3.recorder.ElementsToArrayCache(osi, eles=eles, arg_vals=['stress'])

    # Define the dynamic analysis
    ts_obj = o3.time_series.Path(osi, dt=asig.dt, values=asig.velocity * -1, factor=c_base)
    o3.pattern.Plain(osi, ts_obj)
    o3.Load(osi, nd["R{0}L".format(n_node_rows - 1)], [1., 0.])

    # Run the dynamic analysis
    o3.algorithm.Newton(osi)
    o3.system.SparseGeneral(osi)
    o3.numberer.RCM(osi)
    o3.constraints.Transformation(osi)
    o3.integrator.Newmark(osi, newmark_gamma, newmark_beta)
    o3.rayleigh.Rayleigh(osi, a0, a1, 0, 0)
    o3.analysis.Transient(osi)

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

    while o3.get_time(osi) < analysis_time:
        o3.analyze(osi, 1, analysis_dt)
    o3.wipe(osi)
    outputs = {
        "time": np.arange(0, analysis_time, analysis_dt),
        "rel_disp": [],
        "rel_accel": na.collect(),
        'ele_stresses': es.collect()
    }

    return outputs
Esempio n. 5
0
def site_response(sp, asig, freqs=(0.5, 10), xi=0.03, dy=0.5, analysis_time=None, outs=None,
                  rec_dt=None, etype='implicit', forder=1.0):
    """
    Run seismic analysis of a soil profile

    Parameters
    ----------
    sp: sfsimodels.SoilProfile object
        A soil profile
    asig: eqsig.AccSignal object
        An acceleration signal

    Returns
    -------

    """
    if analysis_time is None:
        analysis_time = asig.time[-1]
    if outs is None:
        outs = {'ACCX': [0]}  # Export the horizontal acceleration at the surface

    osi = o3.OpenSeesInstance(ndm=2, ndf=2, state=3)
    assert isinstance(sp, sm.SoilProfile)
    sp.gen_split(props=['shear_vel', 'unit_mass', 'g_mod', 'poissons_ratio'], target=dy)
    thicknesses = sp.split["thickness"]
    n_node_rows = len(thicknesses) + 1
    node_depths = np.cumsum(sp.split["thickness"])
    node_depths = np.insert(node_depths, 0, 0)
    ele_depths = (node_depths[1:] + node_depths[:-1]) / 2
    rho = sp.split['unit_mass']
    g_mod = sp.split['g_mod']
    poi = sp.split['poissons_ratio']
    lam = 2 * g_mod * poi / (1 - 2 * poi)
    mu = g_mod
    v_dil = np.sqrt((lam + 2 * mu) / rho)
    ele_h = sp.split['thickness']
    dts = ele_h / v_dil
    min_dt = min(dts)
    print('min_dt: ', min_dt)
    grav = 9.81

    ele_width = min(thicknesses)

    # Define nodes and set boundary conditions for simple shear deformation
    # Start at top and build down?
    sn = [[o3.node.Node(osi, 0, 0), o3.node.Node(osi, ele_width, 0)]]
    for i in range(1, n_node_rows):
        # Establish left and right nodes
        sn.append([o3.node.Node(osi, 0, -node_depths[i]),
                    o3.node.Node(osi, ele_width, -node_depths[i])])
        # set x and y dofs equal for left and right nodes
        o3.EqualDOF(osi, sn[i][0], sn[i][1], [o3.cc.X, o3.cc.Y])

    # Fix base nodes
    o3.Fix2DOF(osi, sn[-1][0], o3.cc.FIXED, o3.cc.FIXED)
    o3.Fix2DOF(osi, sn[-1][1], o3.cc.FIXED, o3.cc.FIXED)

    # define materials
    ele_thick = 1.0  # m
    soil_mats = []
    eles = []
    prev_id = -1
    for i in range(len(thicknesses)):
        y_depth = ele_depths[i]

        sl_id = sp.get_layer_index_by_depth(y_depth)
        sl = sp.layer(sl_id)
        mat = sl.o3_mat
        if sl_id != prev_id:
            mat.build(osi)
            soil_mats.append(mat)
            prev_id = sl_id

        # def element
        nodes = [sn[i+1][0], sn[i+1][1], sn[i][1], sn[i][0]]  # anti-clockwise
        eles.append(o3.element.SSPquad(osi, nodes, mat, o3.cc.PLANE_STRAIN, ele_thick, 0.0, -grav))

    for i, soil_mat in enumerate(soil_mats):
        if hasattr(soil_mat, 'update_to_linear'):
            print('Update model to linear')
            soil_mat.update_to_linear()

    # Gravity analysis
    o3.constraints.Transformation(osi)
    o3.test_check.NormDispIncr(osi, tol=1.0e-5, max_iter=30, p_flag=0)
    o3.algorithm.Newton(osi)
    o3.numberer.RCM(osi)
    o3.system.ProfileSPD(osi)
    o3.integrator.Newmark(osi, 5./6, 4./9)  # include numerical damping
    o3.analysis.Transient(osi)
    o3.analyze(osi, 40, 1.)

    for i, soil_mat in enumerate(soil_mats):
        if hasattr(soil_mat, 'update_to_nonlinear'):
            print('Update model to nonlinear')
            soil_mat.update_to_nonlinear()
    if o3.analyze(osi, 50, 0.5):
        print('Model failed')
        return
    print('finished nonlinear gravity analysis')

    # reset time and analysis
    o3.set_time(osi, 0.0)
    o3.wipe_analysis(osi)

    n = 10
    # omegas = np.array(o3.get_eigen(osi, solver='fullGenLapack', n=n)) ** 0.5  # DO NOT USE fullGenLapack
    omegas = np.array(o3.get_eigen(osi, n=n)) ** 0.5
    periods = 2 * np.pi / omegas
    print('response_periods: ', periods)

    o3.constraints.Transformation(osi)
    o3.test_check.NormDispIncr(osi, tol=1.0e-6, max_iter=10)
    o3.numberer.RCM(osi)
    if etype == 'implicit':
        o3.system.ProfileSPD(osi)
        o3.algorithm.NewtonLineSearch(osi, 0.75)
        o3.integrator.Newmark(osi, 0.5, 0.25)
        dt = 0.001
    else:
        o3.algorithm.Linear(osi)
        if etype == 'newmark_explicit':
            o3.system.ProfileSPD(osi)
            o3.integrator.NewmarkExplicit(osi, gamma=0.5)
            explicit_dt = min_dt / 1
        elif etype == 'central_difference':
            o3.system.ProfileSPD(osi)
            o3.integrator.CentralDifference(osi)
            explicit_dt = min_dt / 8
        elif etype == 'explicit_difference':
            o3.system.Diagonal(osi)
            o3.integrator.ExplicitDifference(osi)
            explicit_dt = min_dt / 2  # need reduced time step due to Rayleigh damping
        else:
            raise ValueError(etype)
        ndp = np.ceil(np.log10(explicit_dt))
        if 0.5 * 10 ** ndp < explicit_dt:
            dt = 0.5 * 10 ** ndp
        elif 0.2 * 10 ** ndp < explicit_dt:
            dt = 0.2 * 10 ** ndp
        elif 0.1 * 10 ** ndp < explicit_dt:
            dt = 0.1 * 10 ** ndp
        else:
            raise ValueError(explicit_dt, 0.1 * 10 ** ndp)
        print('explicit_dt: ', explicit_dt, dt)
    use_modal_damping = 0

    if not use_modal_damping:
        omega_1 = 2 * np.pi * freqs[0]
        omega_2 = 2 * np.pi * freqs[1]
        a0 = 2 * xi * omega_1 * omega_2 / (omega_1 + omega_2)
        a1 = 2 * xi / (omega_1 + omega_2)
        o3.rayleigh.Rayleigh(osi, a0, 0, a1, 0)
    else:
        o3.ModalDamping(osi, [xi])
    o3.analysis.Transient(osi)

    o3.test_check.NormDispIncr(osi, tol=1.0e-7, max_iter=10)
    rec_dt = 0.001

    ods = {}
    for otype in outs:
        if otype == 'ACCX':

            ods['ACCX'] = []
            if isinstance(outs['ACCX'], str) and outs['ACCX'] == 'all':
                ods['ACCX'] = o3.recorder.NodesToArrayCache(osi, nodes=sn[:][0], dofs=[o3.cc.X], res_type='accel', dt=rec_dt)
            else:
                for i in range(len(outs['ACCX'])):
                    ind = np.argmin(abs(node_depths - outs['ACCX'][i]))
                    ods['ACCX'].append(o3.recorder.NodeToArrayCache(osi, node=sn[ind][0], dofs=[o3.cc.X], res_type='accel', dt=rec_dt))
        if otype == 'TAU':
            ods['TAU'] = []
            if isinstance(outs['TAU'], str) and outs['TAU'] == 'all':
                ods['TAU'] = o3.recorder.ElementsToArrayCache(osi, eles=eles, arg_vals=['stress'], dt=rec_dt)
            else:
                for i in range(len(outs['TAU'])):
                    ind = np.argmin(abs(ele_depths - outs['TAU'][i]))
                    ods['TAU'].append(o3.recorder.ElementToArrayCache(osi, ele=eles[ind], arg_vals=['stress'], dt=rec_dt))

        if otype == 'STRS':
            ods['STRS'] = []
            if isinstance(outs['STRS'], str) and outs['STRS'] == 'all':
                ods['STRS'] = o3.recorder.ElementsToArrayCache(osi, eles=eles, arg_vals=['strain'], dt=rec_dt)
            else:
                for i in range(len(outs['STRS'])):
                    ind = np.argmin(abs(ele_depths - outs['STRS'][i]))
                    ods['STRS'].append(o3.recorder.ElementToArrayCache(osi, ele=eles[ind], arg_vals=['strain'], dt=rec_dt))
    ods['time'] = o3.recorder.TimeToArrayCache(osi, dt=rec_dt)

    acc_series = o3.time_series.Path(osi, dt=asig.dt, values=asig.values)
    o3.pattern.UniformExcitation(osi, dir=o3.cc.X, accel_series=acc_series)

    # Run the dynamic analysis

    inc = 1
    if etype != 'implicit':
        inc = 10
    o3.record(osi)
    while o3.get_time(osi) < analysis_time:
        print(o3.get_time(osi))
        if o3.analyze(osi, inc, dt):
            print('failed')
            break

    o3.wipe(osi)
    out_dict = {}
    for otype in ods:
        if isinstance(ods[otype], list):
            out_dict[otype] = []
            for i in range(len(ods[otype])):
                out_dict[otype].append(ods[otype][i].collect())
            out_dict[otype] = np.array(out_dict[otype])
        else:
            out_dict[otype] = ods[otype].collect().T

    return out_dict
def site_response(sp,
                  asig,
                  freqs=(0.5, 10),
                  xi=0.03,
                  dtype='rayleigh',
                  analysis_dt=0.001,
                  dy=0.5,
                  analysis_time=None,
                  outs=None,
                  rec_dt=None):
    """
    Run seismic analysis of a soil profile

    Parameters
    ----------
    sp: sfsimodels.SoilProfile object
        A soil profile
    asig: eqsig.AccSignal object
        An acceleration signal

    Returns
    -------

    """
    if analysis_time is None:
        analysis_time = asig.time[-1]
    if outs is None:
        outs = {
            'ACCX': [0]
        }  # Export the horizontal acceleration at the surface
    if rec_dt is None:
        rec_dt = analysis_dt

    osi = o3.OpenSeesInstance(ndm=2, ndf=2, state=3)
    assert isinstance(sp, sm.SoilProfile)
    sp.gen_split(props=['shear_vel', 'unit_mass'], target=dy)
    thicknesses = sp.split["thickness"]
    n_node_rows = len(thicknesses) + 1
    node_depths = np.cumsum(sp.split["thickness"])
    node_depths = np.insert(node_depths, 0, 0)
    ele_depths = (node_depths[1:] + node_depths[:-1]) / 2

    grav = 9.81

    k0 = 0.5
    pois = k0 / (1 + k0)

    newmark_gamma = 0.5
    newmark_beta = 0.25

    ele_width = min(thicknesses)

    # Define nodes and set boundary conditions for simple shear deformation
    # Start at top and build down?
    sn = [[o3.node.Node(osi, 0, 0), o3.node.Node(osi, ele_width, 0)]]
    for i in range(1, n_node_rows):
        # Establish left and right nodes
        sn.append([
            o3.node.Node(osi, 0, -node_depths[i]),
            o3.node.Node(osi, ele_width, -node_depths[i])
        ])
        # set x and y dofs equal for left and right nodes
        o3.EqualDOF(osi, sn[i][0], sn[i][1], [o3.cc.X, o3.cc.Y])

    # Fix base nodes
    o3.Fix2DOF(osi, sn[-1][0], o3.cc.FIXED, o3.cc.FIXED)
    o3.Fix2DOF(osi, sn[-1][1], o3.cc.FIXED, o3.cc.FIXED)

    # define materials
    ele_thick = 1.0  # m
    soil_mats = []
    prev_pms = [0, 0, 0]
    eles = []
    for i in range(len(thicknesses)):
        y_depth = ele_depths[i]
        sl_id = sp.get_layer_index_by_depth(y_depth)
        sl = sp.layer(sl_id)
        # Define material
        e_mod = 2 * sl.g_mod * (1 + sl.poissons_ratio)
        umass = sl.unit_dry_mass
        nu = sl.poissons_ratio
        pms = [e_mod, nu, umass]
        changed = 0
        for pp in range(len(pms)):
            if not np.isclose(pms[pp], prev_pms[pp]):
                changed = 1
        if changed:
            mat = o3.nd_material.ElasticIsotropic(osi,
                                                  e_mod=e_mod,
                                                  nu=nu,
                                                  rho=umass)
            soil_mats.append(mat)

        # def element
        nodes = [sn[i + 1][0], sn[i + 1][1], sn[i][1],
                 sn[i][0]]  # anti-clockwise
        eles.append(
            o3.element.SSPquad(osi, nodes, mat, o3.cc.PLANE_STRAIN, ele_thick,
                               0.0, grav * umass))

    # Static analysis
    o3.constraints.Transformation(osi)
    o3.test_check.NormDispIncr(osi, tol=1.0e-5, max_iter=30, p_flag=0)
    o3.algorithm.Newton(osi)
    o3.numberer.RCM(osi)
    o3.system.ProfileSPD(osi)
    o3.integrator.Newmark(osi, 5. / 6, 4. / 9)  # include numerical damping
    o3.analysis.Transient(osi)
    o3.analyze(osi, 40, 1.)

    # reset time and analysis
    o3.set_time(osi, 0.0)
    o3.wipe_analysis(osi)

    ods = {}
    for otype in outs:
        if otype == 'ACCX':
            ods['ACCX'] = []
            if isinstance(outs['ACCX'], str) and outs['ACCX'] == 'all':
                ods['ACCX'] = o3.recorder.NodesToArrayCache(osi,
                                                            nodes=sn[:][0],
                                                            dofs=[o3.cc.X],
                                                            res_type='accel',
                                                            dt=rec_dt)
            else:
                for i in range(len(outs['ACCX'])):
                    ind = np.argmin(abs(node_depths - outs['ACCX'][i]))
                    ods['ACCX'].append(
                        o3.recorder.NodeToArrayCache(osi,
                                                     node=sn[ind][0],
                                                     dofs=[o3.cc.X],
                                                     res_type='accel',
                                                     dt=rec_dt))

    # Run the dynamic analysis
    o3.algorithm.Newton(osi)
    o3.system.SparseGeneral(osi)
    o3.numberer.RCM(osi)
    o3.constraints.Transformation(osi)
    o3.integrator.Newmark(osi, newmark_gamma, newmark_beta)

    if dtype == 'rayleigh':
        omega_1 = 2 * np.pi * freqs[0]
        omega_2 = 2 * np.pi * freqs[1]
        a0 = 2 * xi * omega_1 * omega_2 / (omega_1 + omega_2)
        a1 = 2 * xi / (omega_1 + omega_2)
        o3.rayleigh.Rayleigh(osi, a0, a1, 0, 0)
    else:
        n = 20
        omegas = np.array(o3.get_eigen(osi, n=n))**0.5
        o3.ModalDamping(osi, [xi])

    o3.analysis.Transient(osi)

    o3.test_check.NormDispIncr(osi, tol=1.0e-7, max_iter=10)

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

    while o3.get_time(osi) < analysis_time:
        print(o3.get_time(osi))
        if o3.analyze(osi, 1, analysis_dt):
            print('failed')
            break

    o3.wipe(osi)
    out_dict = {}
    for otype in ods:
        if isinstance(ods[otype], list):
            out_dict[otype] = []
            for i in range(len(ods[otype])):
                out_dict[otype].append(ods[otype][i].collect())
            out_dict[otype] = np.array(out_dict[otype])
        else:
            out_dict[otype] = ods[otype].collect().T
    out_dict['time'] = np.arange(0, analysis_time, rec_dt)

    return out_dict
Esempio n. 7
0
def site_response(sp, dy=0.5, forder=1.0e3, static=0):
    """
    Run gravity analysis of a soil profile

    Parameters
    ----------
    sp: sfsimodels.SoilProfile object
        A soil profile

    Returns
    -------

    """

    osi = o3.OpenSeesInstance(ndm=2, ndf=2, state=3)
    assert isinstance(sp, sm.SoilProfile)
    sp.gen_split(props=['shear_vel', 'unit_mass', 'g_mod', 'poissons_ratio'],
                 target=dy)
    thicknesses = sp.split["thickness"]
    n_node_rows = len(thicknesses) + 1
    node_depths = np.cumsum(sp.split["thickness"])
    node_depths = np.insert(node_depths, 0, 0)
    ele_depths = (node_depths[1:] + node_depths[:-1]) / 2
    rho = sp.split['unit_mass']
    g_mod = sp.split['g_mod']
    poi = sp.split['poissons_ratio']
    lam = 2 * g_mod * poi / (1 - 2 * poi)
    mu = g_mod
    v_dil = np.sqrt((lam + 2 * mu) / rho)
    ele_h = sp.split['thickness']
    dts = ele_h / v_dil
    min_dt = min(dts)
    print('min_dt: ', min_dt)
    grav = 9.81

    ele_width = min(thicknesses)

    # Define nodes and set boundary conditions for simple shear deformation
    # Start at top and build down?
    sn = [[o3.node.Node(osi, 0, 0), o3.node.Node(osi, ele_width, 0)]]
    for i in range(1, n_node_rows):
        # Establish left and right nodes
        sn.append([
            o3.node.Node(osi, 0, -node_depths[i]),
            o3.node.Node(osi, ele_width, -node_depths[i])
        ])
        # set x and y dofs equal for left and right nodes
        o3.EqualDOF(osi, sn[i][0], sn[i][1], [o3.cc.X, o3.cc.Y])

    # Fix base nodes
    o3.Fix2DOF(osi, sn[-1][0], o3.cc.FIXED, o3.cc.FIXED)
    o3.Fix2DOF(osi, sn[-1][1], o3.cc.FIXED, o3.cc.FIXED)

    # define materials
    ele_thick = 1.0  # m
    soil_mats = []
    prev_args = []
    prev_kwargs = {}
    prev_sl_type = None
    eles = []
    for i in range(len(thicknesses)):
        y_depth = ele_depths[i]

        sl_id = sp.get_layer_index_by_depth(y_depth)
        sl = sp.layer(sl_id)
        app2mod = {}
        if y_depth > sp.gwl:
            umass = sl.unit_sat_mass / forder
        else:
            umass = sl.unit_dry_mass / forder
        # Define material
        sl_class = o3.nd_material.ElasticIsotropic
        sl.e_mod = 2 * sl.g_mod * (1 + sl.poissons_ratio) / forder
        app2mod['rho'] = 'unit_moist_mass'
        overrides = {'nu': sl.poissons_ratio, 'unit_moist_mass': umass}

        args, kwargs = o3.extensions.get_o3_kwargs_from_obj(
            sl, sl_class, custom=app2mod, overrides=overrides)
        changed = 0
        if sl.type != prev_sl_type or len(args) != len(prev_args) or len(
                kwargs) != len(prev_kwargs):
            changed = 1
        else:
            for j, arg in enumerate(args):
                if not np.isclose(arg, prev_args[j]):
                    changed = 1
            for pm in kwargs:
                if pm not in prev_kwargs or not np.isclose(
                        kwargs[pm], prev_kwargs[pm]):
                    changed = 1

        if changed:
            mat = sl_class(osi, *args, **kwargs)
            prev_sl_type = sl.type
            prev_args = copy.deepcopy(args)
            prev_kwargs = copy.deepcopy(kwargs)

            soil_mats.append(mat)

        # def element
        nodes = [sn[i + 1][0], sn[i + 1][1], sn[i][1],
                 sn[i][0]]  # anti-clockwise
        eles.append(
            o3.element.SSPquad(osi, nodes, mat, o3.cc.PLANE_STRAIN, ele_thick,
                               0.0, -grav))

    # Gravity analysis
    if not static:
        o3.constraints.Transformation(osi)
        o3.test_check.NormDispIncr(osi, tol=1.0e-5, max_iter=30, p_flag=0)
        o3.algorithm.Newton(osi)
        o3.numberer.RCM(osi)
        o3.system.ProfileSPD(osi)
        o3.integrator.Newmark(osi, 5. / 6, 4. / 9)  # include numerical damping
        o3.analysis.Transient(osi)
        o3.analyze(osi, 40, 1.)

        # reset time and analysis
        o3.set_time(osi, 0.0)
        o3.wipe_analysis(osi)
    else:
        o3.domain_change(osi)
        o3.constraints.Transformation(osi)
        o3.test_check.NormDispIncr(osi, tol=1.0e-6, max_iter=10, p_flag=0)
        o3.algorithm.Newton(osi)
        o3.numberer.apply_rcm(osi)
        o3.system.Mumps(osi)
        o3.integrator.LoadControl(osi, 0.1)
        o3.analysis.Static(osi)
        o3.analyze(osi, 10)

    ys = []
    for nn in range(len(sn)):
        nd = sn[nn][0]
        ys.append(o3.get_node_disp(osi, nd, dof=o3.cc.Y))

    return np.array(ys)
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 site_response(sp,
                  asig,
                  freqs=(0.5, 10),
                  xi=0.03,
                  analysis_dt=0.001,
                  dy=0.5,
                  analysis_time=None,
                  outs=None,
                  rec_dt=None,
                  forder=1.0e3):
    """
    Run seismic analysis of a soil profile

    Parameters
    ----------
    sp: sfsimodels.SoilProfile object
        A soil profile
    asig: eqsig.AccSignal object
        An acceleration signal

    Returns
    -------

    """
    if analysis_time is None:
        analysis_time = asig.time[-1]
    if outs is None:
        outs = {
            'ACCX': [0]
        }  # Export the horizontal acceleration at the surface
    if rec_dt is None:
        rec_dt = analysis_dt

    osi = o3.OpenSeesInstance(ndm=2, ndf=2, state=3)
    assert isinstance(sp, sm.SoilProfile)
    sp.gen_split(props=['shear_vel', 'unit_mass'], target=dy)
    thicknesses = sp.split["thickness"]
    n_node_rows = len(thicknesses) + 1
    node_depths = np.cumsum(sp.split["thickness"])
    node_depths = np.insert(node_depths, 0, 0)
    ele_depths = (node_depths[1:] + node_depths[:-1]) / 2

    grav = 9.81
    omega_1 = 2 * np.pi * freqs[0]
    omega_2 = 2 * np.pi * freqs[1]
    a0 = 2 * xi * omega_1 * omega_2 / (omega_1 + omega_2)
    a1 = 2 * xi / (omega_1 + omega_2)

    k0 = 0.5
    pois = k0 / (1 + k0)

    newmark_gamma = 0.5
    newmark_beta = 0.25

    ele_width = min(thicknesses)

    # Define nodes and set boundary conditions for simple shear deformation
    # Start at top and build down?
    sn = [[o3.node.Node(osi, 0, 0), o3.node.Node(osi, ele_width, 0)]]
    for i in range(1, n_node_rows):
        # Establish left and right nodes
        sn.append([
            o3.node.Node(osi, 0, -node_depths[i]),
            o3.node.Node(osi, ele_width, -node_depths[i])
        ])
        if i != n_node_rows - 1:
            # set x and y dofs equal for left and right nodes
            o3.EqualDOF(osi, sn[i][0], sn[i][1], [o3.cc.X, o3.cc.Y])

    # Fix base nodes
    o3.Fix2DOF(osi, sn[-1][0], o3.cc.FREE, o3.cc.FIXED)
    o3.Fix2DOF(osi, sn[-1][1], o3.cc.FREE, o3.cc.FIXED)
    o3.EqualDOF(osi, sn[-1][0], sn[-1][1], [o3.cc.X])

    # Define dashpot nodes
    dashpot_node_1 = o3.node.Node(osi, 0, -node_depths[-1])
    dashpot_node_2 = sn[-1][0]
    o3.Fix2DOF(osi, dashpot_node_1, o3.cc.FIXED, o3.cc.FIXED)

    # define materials
    ele_thick = 1.0  # m
    soil_mats = []
    prev_args = []
    prev_kwargs = {}
    prev_sl_type = None
    eles = []
    for i in range(len(thicknesses)):
        y_depth = ele_depths[i]

        sl_id = sp.get_layer_index_by_depth(y_depth)
        sl = sp.layer(sl_id)
        app2mod = {}
        if y_depth > sp.gwl:
            umass = sl.unit_sat_mass / forder
        else:
            umass = sl.unit_dry_mass / forder
        p_atm = 101e3 / forder
        # Define material
        if sl.o3_type == 'pm4sand':
            sl_class = o3.nd_material.PM4Sand
            overrides = {'nu': pois, 'p_atm': p_atm, 'unit_moist_mass': umass}
            app2mod = sl.app2mod
        elif sl.o3_type == 'sdmodel':
            sl_class = o3.nd_material.StressDensity
            overrides = {'nu': pois, 'p_atm': p_atm, 'unit_moist_mass': umass}
            app2mod = sl.app2mod
        elif sl.o3_type == 'pimy':
            sl_class = o3.nd_material.PressureIndependMultiYield
            overrides = {
                'nu': pois,
                'p_atm': p_atm,
                'rho': umass,
                'nd': 2.0,
                'g_mod_ref': sl.g_mod / forder,
                'bulk_mod_ref': sl.bulk_mod / forder,
                'peak_strain': 0.05,
                'cohesion': sl.cohesion / forder,
                'phi': sl.phi,
                'p_ref': 101e3 / forder,
                'd': 0.0,
                'n_surf': 25
            }
        else:
            sl_class = o3.nd_material.ElasticIsotropic
            sl.e_mod = 2 * sl.g_mod * (1 + sl.poissons_ratio) / forder
            app2mod['rho'] = 'unit_moist_mass'
            overrides = {'nu': sl.poissons_ratio, 'unit_moist_mass': umass}

        args, kwargs = o3.extensions.get_o3_kwargs_from_obj(
            sl, sl_class, custom=app2mod, overrides=overrides)
        changed = 0
        if sl.type != prev_sl_type or len(args) != len(prev_args) or len(
                kwargs) != len(prev_kwargs):
            changed = 1
        else:
            for j, arg in enumerate(args):
                if not np.isclose(arg, prev_args[j]):
                    changed = 1
            for pm in kwargs:
                if pm not in prev_kwargs or not np.isclose(
                        kwargs[pm], prev_kwargs[pm]):
                    changed = 1

        if changed:
            mat = sl_class(osi, *args, **kwargs)
            prev_sl_type = sl.type
            prev_args = copy.deepcopy(args)
            prev_kwargs = copy.deepcopy(kwargs)

            soil_mats.append(mat)

        # def element
        nodes = [sn[i + 1][0], sn[i + 1][1], sn[i][1],
                 sn[i][0]]  # anti-clockwise
        eles.append(
            o3.element.SSPquad(osi, nodes, mat, o3.cc.PLANE_STRAIN, ele_thick,
                               0.0, -grav))

    # Static analysis
    o3.constraints.Transformation(osi)
    o3.test_check.NormDispIncr(osi, tol=1.0e-5, max_iter=30, p_flag=0)
    o3.algorithm.Newton(osi)
    o3.numberer.RCM(osi)
    o3.system.ProfileSPD(osi)
    o3.integrator.Newmark(osi, 5. / 6, 4. / 9)  # include numerical damping
    o3.analysis.Transient(osi)
    o3.analyze(osi, 40, 1.)

    for i, soil_mat in enumerate(soil_mats):
        if hasattr(soil_mat, 'update_to_nonlinear'):
            print('Update model to nonlinear')
            soil_mat.update_to_nonlinear()
    o3.analyze(osi, 50, 0.5)
    o3.extensions.to_py_file(osi, 'ofile_sra_pimy_og.py')

    # reset time and analysis
    o3.set_time(osi, 0.0)
    o3.wipe_analysis(osi)

    # define material and element for viscous dampers
    base_sl = sp.layer(sp.n_layers)
    c_base = ele_width * base_sl.unit_dry_mass / forder * sp.get_shear_vel_at_depth(
        sp.height)
    dashpot_mat = o3.uniaxial_material.Viscous(osi, c_base, alpha=1.)
    o3.element.ZeroLength(osi, [dashpot_node_1, dashpot_node_2],
                          mats=[dashpot_mat],
                          dirs=[o3.cc.DOF2D_X])

    ods = {}
    for otype in outs:
        if otype == 'ACCX':

            ods['ACCX'] = []
            if isinstance(outs['ACCX'], str) and outs['ACCX'] == 'all':
                ods['ACCX'] = o3.recorder.NodesToArrayCache(osi,
                                                            nodes=sn[:][0],
                                                            dofs=[o3.cc.X],
                                                            res_type='accel',
                                                            dt=rec_dt)
            else:
                for i in range(len(outs['ACCX'])):
                    ind = np.argmin(abs(node_depths - outs['ACCX'][i]))
                    ods['ACCX'].append(
                        o3.recorder.NodeToArrayCache(osi,
                                                     node=sn[ind][0],
                                                     dofs=[o3.cc.X],
                                                     res_type='accel',
                                                     dt=rec_dt))
        if otype == 'TAU':
            ods['TAU'] = []
            if isinstance(outs['TAU'], str) and outs['TAU'] == 'all':
                ods['TAU'] = o3.recorder.ElementsToArrayCache(
                    osi, eles=eles, arg_vals=['stress'], dt=rec_dt)
            else:
                for i in range(len(outs['TAU'])):
                    ind = np.argmin(abs(ele_depths - outs['TAU'][i]))
                    ods['TAU'].append(
                        o3.recorder.ElementToArrayCache(osi,
                                                        ele=eles[ind],
                                                        arg_vals=['stress'],
                                                        dt=rec_dt))

        if otype == 'STRS':
            ods['STRS'] = []
            if isinstance(outs['STRS'], str) and outs['STRS'] == 'all':
                ods['STRS'] = o3.recorder.ElementsToArrayCache(
                    osi, eles=eles, arg_vals=['strain'], dt=rec_dt)
            else:
                for i in range(len(outs['STRS'])):
                    ind = np.argmin(abs(ele_depths - outs['STRS'][i]))
                    ods['STRS'].append(
                        o3.recorder.ElementToArrayCache(osi,
                                                        ele=eles[ind],
                                                        arg_vals=['strain'],
                                                        dt=rec_dt))
    ods['time'] = o3.recorder.TimeToArrayCache(osi, dt=rec_dt)

    # Run the dynamic analysis
    o3.algorithm.Newton(osi)
    o3.system.SparseGeneral(osi)
    o3.numberer.RCM(osi)
    o3.constraints.Transformation(osi)
    o3.integrator.Newmark(osi, newmark_gamma, newmark_beta)
    o3.rayleigh.Rayleigh(osi, a0, a1, 0, 0)
    o3.analysis.Transient(osi)
    o3.test_check.EnergyIncr(osi, tol=1.0e-7, max_iter=10)

    # Define the dynamic analysis
    ts_obj = o3.time_series.Path(osi,
                                 dt=asig.dt,
                                 values=asig.velocity * 1,
                                 factor=c_base)
    o3.pattern.Plain(osi, ts_obj)
    o3.Load(osi, sn[-1][0], [1., 0.])

    o3.analyze(osi, int(analysis_time / analysis_dt), analysis_dt)

    o3.wipe(osi)
    out_dict = {}
    for otype in ods:
        if isinstance(ods[otype], list):
            out_dict[otype] = []
            for i in range(len(ods[otype])):
                out_dict[otype].append(ods[otype][i].collect())
            out_dict[otype] = np.array(out_dict[otype])
        else:
            out_dict[otype] = ods[otype].collect().T

    return out_dict
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
Esempio n. 11
0
def site_response(sp,
                  asig,
                  freqs=(0.5, 10),
                  xi=0.03,
                  dy=0.5,
                  analysis_time=None,
                  outs=None,
                  rec_dt=None,
                  etype='variable',
                  forder=1.0):
    """
    Run seismic analysis of a soil profile

    Parameters
    ----------
    sp: sfsimodels.SoilProfile object
        A soil profile
    asig: eqsig.AccSignal object
        An acceleration signal

    Returns
    -------

    """
    if analysis_time is None:
        analysis_time = asig.time[-1]
    if outs is None:
        outs = {
            'ACCX': [0]
        }  # Export the horizontal acceleration at the surface

    osi = o3.OpenSeesInstance(ndm=2, ndf=2, state=3)
    assert isinstance(sp, sm.SoilProfile)
    sp.gen_split(props=['shear_vel', 'unit_mass'], target=dy)
    req_dt = min(sp.split["thickness"] / sp.split['shear_vel']) / 8
    thicknesses = sp.split["thickness"]
    n_node_rows = len(thicknesses) + 1
    node_depths = np.cumsum(sp.split["thickness"])
    node_depths = np.insert(node_depths, 0, 0)
    ele_depths = (node_depths[1:] + node_depths[:-1]) / 2
    unit_masses = sp.split["unit_mass"] / forder

    grav = 9.81

    ele_width = min(thicknesses)
    total_soil_nodes = len(thicknesses) * 2 + 2

    # Define nodes and set boundary conditions for simple shear deformation
    # Start at top and build down?
    sn = [[o3.node.Node(osi, 0, 0), o3.node.Node(osi, ele_width, 0)]]
    for i in range(1, n_node_rows):
        # Establish left and right nodes
        sn.append([
            o3.node.Node(osi, 0, -node_depths[i]),
            o3.node.Node(osi, ele_width, -node_depths[i])
        ])
        # set x and y dofs equal for left and right nodes
        o3.EqualDOF(osi, sn[i][0], sn[i][1], [o3.cc.X, o3.cc.Y])

    # Fix base nodes
    o3.Fix2DOF(osi, sn[-1][0], o3.cc.FIXED, o3.cc.FIXED)
    o3.Fix2DOF(osi, sn[-1][1], o3.cc.FIXED, o3.cc.FIXED)

    # define materials
    ele_thick = 1.0  # m
    soil_mats = []
    eles = []
    prev_id = -1
    for i in range(len(thicknesses)):
        y_depth = ele_depths[i]

        sl_id = sp.get_layer_index_by_depth(y_depth)
        sl = sp.layer(sl_id)
        mat = sl.o3_mat
        if sl_id != prev_id:
            mat.build(osi)
            soil_mats.append(mat)
            prev_id = sl_id

        # def element
        nodes = [sn[i + 1][0], sn[i + 1][1], sn[i][1],
                 sn[i][0]]  # anti-clockwise
        eles.append(
            o3.element.SSPquad(osi, nodes, mat, o3.cc.PLANE_STRAIN, ele_thick,
                               0.0, -grav))

    for i, soil_mat in enumerate(soil_mats):
        if hasattr(soil_mat, 'update_to_linear'):
            print('Update model to linear')
            soil_mat.update_to_linear()

    # Gravity analysis
    o3.constraints.Transformation(osi)
    o3.test_check.NormDispIncr(osi, tol=1.0e-5, max_iter=30, p_flag=0)
    o3.algorithm.Newton(osi)
    o3.numberer.RCM(osi)
    o3.system.ProfileSPD(osi)
    o3.integrator.Newmark(osi, 5. / 6, 4. / 9)  # include numerical damping
    if etype == 'variable':
        o3.analysis.VariableTransient(osi)
        g_args = [0.5, 0.1, 2, 5]
    else:
        o3.analysis.Transient(osi)
        g_args = [0.5]
    fail = o3.analyze(osi, 40, *g_args)
    if fail:
        return

    for i, soil_mat in enumerate(soil_mats):
        if hasattr(soil_mat, 'update_to_nonlinear'):
            print('Update model to nonlinear')
            soil_mat.update_to_nonlinear()
    if o3.analyze(osi, 50, *g_args):
        print('Model failed')
        return
    print('finished nonlinear gravity analysis')

    # reset time and analysis
    o3.set_time(osi, 0.0)
    o3.wipe_analysis(osi)

    n = 10
    # omegas = np.array(o3.get_eigen(osi, solver='fullGenLapack', n=n)) ** 0.5  # DO NOT USE fullGenLapack
    omegas = np.array(o3.get_eigen(osi, n=n))**0.5
    periods = 2 * np.pi / omegas
    print('response_periods: ', periods)

    o3.constraints.Transformation(osi)
    o3.test_check.NormDispIncr(osi, tol=1.0e-8, max_iter=6, p_flag=0)
    o3.numberer.RCM(osi)
    o3.system.ProfileSPD(osi)
    # o3.algorithm.NewtonLineSearch(osi, 0.75)
    # o3.integrator.Newmark(osi, 0.5, 0.25)
    if etype == 'variable':
        # o3.analysis.VariableTransient(osi)
        o3.opy.analysis('VariableTransient')
        dyn_args = [20, 0.01, 0.0005, 0.01, 3]
    else:
        o3.analysis.Transient(osi)
        dyn_args = [20, 0.01]
    omega_1 = 2 * np.pi * freqs[0]
    omega_2 = 2 * np.pi * freqs[1]
    a0 = 2 * xi * omega_1 * omega_2 / (omega_1 + omega_2)
    a1 = 2 * xi / (omega_1 + omega_2)
    o3.rayleigh.Rayleigh(osi, a0, 0, a1, 0)
    o3.extensions.to_py_file(osi, f'opy_var_integration_{etype}.py')

    # o3.test_check.NormDispIncr(osi, tol=1.0e-7, max_iter=10)
    rec_dt = 0.001

    ods = {}
    for otype in outs:
        if otype == 'ACCX':

            ods['ACCX'] = []
            if isinstance(outs['ACCX'], str) and outs['ACCX'] == 'all':
                ods['ACCX'] = o3.recorder.NodesToArrayCache(osi,
                                                            nodes=sn[:][0],
                                                            dofs=[o3.cc.X],
                                                            res_type='accel',
                                                            dt=rec_dt)
            else:
                for i in range(len(outs['ACCX'])):
                    ind = np.argmin(abs(node_depths - outs['ACCX'][i]))
                    ods['ACCX'].append(
                        o3.recorder.NodeToArrayCache(osi,
                                                     node=sn[ind][0],
                                                     dofs=[o3.cc.X],
                                                     res_type='accel',
                                                     dt=rec_dt))
        if otype == 'TAU':
            ods['TAU'] = []
            if isinstance(outs['TAU'], str) and outs['TAU'] == 'all':
                ods['TAU'] = o3.recorder.ElementsToArrayCache(
                    osi, eles=eles, arg_vals=['stress'], dt=rec_dt)
            else:
                for i in range(len(outs['TAU'])):
                    ind = np.argmin(abs(ele_depths - outs['TAU'][i]))
                    ods['TAU'].append(
                        o3.recorder.ElementToArrayCache(osi,
                                                        ele=eles[ind],
                                                        arg_vals=['stress'],
                                                        dt=rec_dt))

        if otype == 'STRS':
            ods['STRS'] = []
            if isinstance(outs['STRS'], str) and outs['STRS'] == 'all':
                ods['STRS'] = o3.recorder.ElementsToArrayCache(
                    osi, eles=eles, arg_vals=['strain'], dt=rec_dt)
            else:
                for i in range(len(outs['STRS'])):
                    ind = np.argmin(abs(ele_depths - outs['STRS'][i]))
                    ods['STRS'].append(
                        o3.recorder.ElementToArrayCache(osi,
                                                        ele=eles[ind],
                                                        arg_vals=['strain'],
                                                        dt=rec_dt))
    ods['time'] = o3.recorder.TimeToArrayCache(osi, dt=rec_dt)

    acc_series = o3.time_series.Path(osi, dt=asig.dt, values=asig.values)
    o3.pattern.UniformExcitation(osi, dir=o3.cc.X, accel_series=acc_series)

    # Run the dynamic analysis

    inc = 1
    o3.record(osi)
    while o3.get_time(osi) < analysis_time:
        print(o3.get_time(osi))
        if o3.analyze(osi, *dyn_args):
            print('failed')
            break

    o3.wipe(osi)
    out_dict = {}
    for otype in ods:
        if isinstance(ods[otype], list):
            out_dict[otype] = []
            for i in range(len(ods[otype])):
                out_dict[otype].append(ods[otype][i].collect())
            out_dict[otype] = np.array(out_dict[otype])
        else:
            out_dict[otype] = ods[otype].collect().T

    return out_dict