Ejemplo n.º 1
0
def enforce_length(x, y, z, L0):
    r0 = np.sum(L0)
    xn = x.copy()
    yn = y.copy()
    zn = z.copy()
    rn = util.arc_length(np.c_[xn, yn, zn])

    while np.abs(rn[-1] - r0) > 1e-5:
        L  = np.diff(rn)
        xn[1:] = (L0/L) * (xn[1:]-xn[:-1]) + xn[:-1]
        zn[1:] = (L0/L) * (zn[1:]-zn[:-1]) + zn[:-1]
        rn = util.arc_length(np.c_[xn, yn, zn])
    return xn, zn
Ejemplo n.º 2
0
 def _compute_s(self):
     """
     compute normalized curve length
     """
     s = arc_length(self.points)
     self.length = s[-1]
     self.ds = np.diff(s)
     self.s = s / s[-1]
Ejemplo n.º 3
0
    def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):

        ##############################
        def region_stacking(i, idx, start_nd_arc, end_nd_arc, layer_name,
                            layer_thickness, fiber_orientation, layer_mat,
                            material_dict, materials, region_loc):
            # Recieve start and end of composite sections chordwise, find which composites layers are in each
            # chordwise regions, generate the precomp composite class instance

            # error handling to makes sure there were no numeric errors causing values very close too, but not exactly, 0 or 1
            start_nd_arc = [
                0. if start_nd_arci != 0. and np.isclose(start_nd_arci, 0.)
                else start_nd_arci for start_nd_arci in start_nd_arc
            ]
            end_nd_arc = [
                0. if end_nd_arci != 0. and np.isclose(end_nd_arci, 0.) else
                end_nd_arci for end_nd_arci in end_nd_arc
            ]
            start_nd_arc = [
                1. if start_nd_arci != 1. and np.isclose(start_nd_arci, 1.)
                else start_nd_arci for start_nd_arci in start_nd_arc
            ]
            end_nd_arc = [
                1. if end_nd_arci != 1. and np.isclose(end_nd_arci, 1.) else
                end_nd_arci for end_nd_arci in end_nd_arc
            ]

            # region end points
            dp = sorted(list(set(start_nd_arc + end_nd_arc)))

            #initialize
            n_plies = []
            thk = []
            theta = []
            mat_idx = []

            # loop through division points, find what layers make up the stack between those bounds
            for i_reg, (dp0, dp1) in enumerate(zip(dp[0:-1], dp[1:])):
                n_pliesi = []
                thki = []
                thetai = []
                mati = []
                for i_sec, start_nd_arci, end_nd_arci in zip(
                        idx, start_nd_arc, end_nd_arc):
                    name = layer_name[i_sec]
                    if start_nd_arci <= dp0 and end_nd_arci >= dp1:

                        if name in region_loc.keys():
                            if region_loc[name][i] == None:
                                region_loc[name][i] = [i_reg]
                            else:
                                region_loc[name][i].append(i_reg)

                        n_pliesi.append(1.)
                        thki.append(layer_thickness[i_sec])
                        if fiber_orientation[i_sec] == None:
                            thetai.append(0.)
                        else:
                            thetai.append(fiber_orientation[i_sec])
                        mati.append(material_dict[layer_mat[i_sec]])

                n_plies.append(np.array(n_pliesi))
                thk.append(np.array(thki))
                theta.append(np.array(thetai))
                mat_idx.append(np.array(mati))

            # print('----------------------')
            # print('dp', dp)
            # print('n_plies', n_plies)
            # print('thk', thk)
            # print('theta', theta)
            # print('mat_idx', mat_idx)
            # print('materials', materials)

            sec = CompositeSection(dp, n_plies, thk, theta, mat_idx, materials)
            return sec, region_loc
            ##############################

        def web_stacking(i, web_idx, web_start_nd_arc, web_end_nd_arc,
                         layer_thickness, fiber_orientation, layer_mat,
                         material_dict, materials, flatback, upperCSi):
            dp = []
            n_plies = []
            thk = []
            theta = []
            mat_idx = []

            if len(web_idx) > 0:
                dp = np.mean(
                    (np.abs(web_start_nd_arc), np.abs(web_start_nd_arc)),
                    axis=0).tolist()

                dp_all = [[-1. * start_nd_arci, -1. * end_nd_arci]
                          for start_nd_arci, end_nd_arci in zip(
                              web_start_nd_arc, web_end_nd_arc)]
                web_dp, web_ids = np.unique(dp_all,
                                            axis=0,
                                            return_inverse=True)
                for webi in np.unique(web_ids):
                    # store variable values (thickness, orientation, material) for layers that make up each web, based on the mapping array web_ids
                    n_pliesi = [
                        1. for i_reg, web_idi in zip(web_idx, web_ids)
                        if web_idi == webi
                    ]
                    thki = [
                        layer_thickness[i_reg]
                        for i_reg, web_idi in zip(web_idx, web_ids)
                        if web_idi == webi
                    ]
                    thetai = [
                        fiber_orientation[i_reg]
                        for i_reg, web_idi in zip(web_idx, web_ids)
                        if web_idi == webi
                    ]
                    thetai = [
                        0. if theta_ij == None else theta_ij
                        for theta_ij in thetai
                    ]
                    mati = [
                        material_dict[layer_mat[i_reg]]
                        for i_reg, web_idi in zip(web_idx, web_ids)
                        if web_idi == webi
                    ]

                    n_plies.append(np.array(n_pliesi))
                    thk.append(np.array(thki))
                    theta.append(np.array(thetai))
                    mat_idx.append(np.array(mati))

            if flatback:
                dp.append(1.)
                n_plies.append(upperCSi.n_plies[-1])
                thk.append(upperCSi.t[-1])
                theta.append(upperCSi.theta[-1])
                mat_idx.append(upperCSi.mat_idx[-1])

            dp_out = sorted(list(set(dp)))

            sec = CompositeSection(dp_out, n_plies, thk, theta, mat_idx,
                                   materials)
            return sec
            ##############################

        layer_name = self.options['modeling_options']['blade']['layer_name']
        layer_mat = self.options['modeling_options']['blade']['layer_mat']

        upperCS = [None] * self.n_span
        lowerCS = [None] * self.n_span
        websCS = [None] * self.n_span
        profile = [None] * self.n_span

        # Check that the layer to be optimized actually exist
        te_ss_var_ok = False
        te_ps_var_ok = False
        spar_cap_ss_var_ok = False
        spar_cap_ps_var_ok = False
        for i_layer in range(self.n_layers):
            if layer_name[i_layer] == self.te_ss_var:
                te_ss_var_ok = True
            if layer_name[i_layer] == self.te_ps_var:
                te_ps_var_ok = True
            if layer_name[i_layer] == self.spar_cap_ss_var:
                spar_cap_ss_var_ok = True
            if layer_name[i_layer] == self.spar_cap_ps_var:
                spar_cap_ps_var_ok = True

        if te_ss_var_ok == False:
            print(
                'The layer at the trailing edge suction side is set to be optimized, but does not exist in the input yaml. Please check.'
            )
        if te_ps_var_ok == False:
            print(
                'The layer at the trailing edge pressure side is set to be optimized, but does not exist in the input yaml. Please check.'
            )
        if spar_cap_ss_var_ok == False:
            print(
                'The layer at the spar cap suction side is set to be optimized, but does not exist in the input yaml. Please check.'
            )
        if spar_cap_ps_var_ok == False:
            print(
                'The layer at the spar cap pressure side is set to be optimized, but does not exist in the input yaml. Please check.'
            )
        region_loc_vars = [
            self.te_ss_var, self.te_ps_var, self.spar_cap_ss_var,
            self.spar_cap_ps_var
        ]

        region_loc_ss = {
        }  # track precomp regions for user selected composite layers
        region_loc_ps = {}
        for var in region_loc_vars:
            region_loc_ss[var] = [None] * self.n_span
            region_loc_ps[var] = [None] * self.n_span

        ## Materials
        material_dict = {}
        materials = []
        for i_mat in range(self.n_mat):
            materials.append(
                Orthotropic2DMaterial(inputs['E'][i_mat, 0], inputs['E'][i_mat,
                                                                         1],
                                      inputs['G'][i_mat,
                                                  0], inputs['nu'][i_mat, 0],
                                      inputs['rho'][i_mat],
                                      discrete_inputs['mat_name'][i_mat]))
            material_dict[discrete_inputs['mat_name'][i_mat]] = i_mat

        ## Spanwise
        for i in range(self.n_span):
            # time0 = time.time()

            ## Profiles
            # rotate
            profile_i = inputs['coord_xy_interp'][i, :, :]
            profile_i_rot = np.column_stack(
                rotate(inputs['pitch_axis'][i], 0., profile_i[:, 0],
                       profile_i[:, 1], np.radians(inputs['theta'][i])))

            # import matplotlib.pyplot as plt
            # plt.plot(profile_i[:,0], profile_i[:,1])
            # plt.plot(profile_i_rot[:,0], profile_i_rot[:,1])
            # plt.axis('equal')
            # plt.title(i)
            # plt.show()

            # normalize
            profile_i_rot[:, 0] -= min(profile_i_rot[:, 0])
            profile_i_rot = profile_i_rot / max(profile_i_rot[:, 0])

            profile_i_rot_precomp = copy.copy(profile_i_rot)
            idx_s = 0
            idx_le_precomp = np.argmax(profile_i_rot_precomp[:, 0])
            if idx_le_precomp != 0:

                if profile_i_rot_precomp[0, 0] == profile_i_rot_precomp[-1, 0]:
                    idx_s = 1
                profile_i_rot_precomp = np.row_stack(
                    (profile_i_rot_precomp[idx_le_precomp:],
                     profile_i_rot_precomp[idx_s:idx_le_precomp, :]))
            profile_i_rot_precomp[:, 1] -= profile_i_rot_precomp[
                np.argmin(profile_i_rot_precomp[:, 0]), 1]

            # # renormalize
            profile_i_rot_precomp[:, 0] -= min(profile_i_rot_precomp[:, 0])
            profile_i_rot_precomp = profile_i_rot_precomp / max(
                profile_i_rot_precomp[:, 0])

            if profile_i_rot_precomp[-1, 0] != 1.:
                profile_i_rot_precomp = np.row_stack(
                    (profile_i_rot_precomp, profile_i_rot_precomp[0, :]))

            # 'web' at trailing edge needed for flatback airfoils
            if profile_i_rot_precomp[0, 1] != profile_i_rot_precomp[
                    -1, 1] and profile_i_rot_precomp[
                        0, 0] == profile_i_rot_precomp[-1, 0]:
                flatback = True
            else:
                flatback = False

            profile[i] = Profile.initWithTEtoTEdata(
                profile_i_rot_precomp[:, 0], profile_i_rot_precomp[:, 1])

            # import matplotlib.pyplot as plt
            # plt.plot(profile_i_rot_precomp[:,0], profile_i_rot_precomp[:,1])
            # plt.axis('equal')
            # plt.title(i)
            # plt.show()

            idx_le = np.argmin(profile_i_rot[:, 0])

            profile_i_arc = arc_length(profile_i_rot)
            arc_L = profile_i_arc[-1]
            profile_i_arc /= arc_L

            loc_LE = profile_i_arc[idx_le]
            len_PS = 1. - loc_LE

            ## Composites
            ss_idx = []
            ss_start_nd_arc = []
            ss_end_nd_arc = []
            ps_idx = []
            ps_start_nd_arc = []
            ps_end_nd_arc = []
            web_start_nd_arc = []
            web_end_nd_arc = []
            web_idx = []

            # Determine spanwise composite layer elements that are non-zero at this spanwise location,
            # determine their chord-wise start and end location on the pressure and suctions side

            spline_arc2xnd = PchipInterpolator(profile_i_arc, profile_i_rot[:,
                                                                            0])

            # time1 = time.time()
            for idx_sec in range(self.n_layers):
                if discrete_inputs['definition_layer'][idx_sec] != 10:
                    if inputs['layer_thickness'][idx_sec, i] != 0.:
                        if inputs['layer_start_nd'][
                                idx_sec, i] < loc_LE or inputs['layer_end_nd'][
                                    idx_sec, i] < loc_LE:
                            ss_idx.append(idx_sec)
                            if inputs['layer_start_nd'][idx_sec, i] < loc_LE:
                                # ss_start_nd_arc.append(sec['start_nd_arc']['values'][i])
                                ss_end_nd_arc_temp = float(
                                    spline_arc2xnd(
                                        inputs['layer_start_nd'][idx_sec, i]))
                                if ss_end_nd_arc_temp > 1 or ss_end_nd_arc_temp < 0:
                                    exit(
                                        'Error in the definition of material '
                                        + layer_name[idx_sec] +
                                        '. It cannot fit in the section number '
                                        + str(i) + ' at span location ' +
                                        str(inputs['r'][i] / inputs['r'][-1] *
                                            100.) + ' %.')
                                if ss_end_nd_arc_temp == profile_i_rot[
                                        0, 0] and profile_i_rot[0, 0] != 1.:
                                    ss_end_nd_arc_temp = 1.
                                ss_end_nd_arc.append(ss_end_nd_arc_temp)
                            else:
                                ss_end_nd_arc.append(1.)
                            # ss_end_nd_arc.append(min(sec['end_nd_arc']['values'][i], loc_LE)/loc_LE)
                            if inputs['layer_end_nd'][idx_sec, i] < loc_LE:
                                ss_start_nd_arc.append(
                                    float(
                                        spline_arc2xnd(
                                            inputs['layer_end_nd'][idx_sec,
                                                                   i])))
                            else:
                                ss_start_nd_arc.append(0.)

                        if inputs['layer_start_nd'][
                                idx_sec, i] > loc_LE or inputs['layer_end_nd'][
                                    idx_sec, i] > loc_LE:
                            ps_idx.append(idx_sec)
                            # ps_start_nd_arc.append((max(sec['start_nd_arc']['values'][i], loc_LE)-loc_LE)/len_PS)
                            # ps_end_nd_arc.append((min(sec['end_nd_arc']['values'][i], 1.)-loc_LE)/len_PS)

                            if inputs['layer_start_nd'][
                                    idx_sec,
                                    i] > loc_LE and inputs['layer_end_nd'][
                                        idx_sec, i] < loc_LE:
                                # ps_start_nd_arc.append(float(remap2grid(profile_i_arc, profile_i_rot[:,0], sec['start_nd_arc']['values'][i])))
                                ps_end_nd_arc.append(1.)
                            else:
                                ps_end_nd_arc_temp = float(
                                    spline_arc2xnd(
                                        inputs['layer_end_nd'][idx_sec, i]))
                                if np.isclose(ps_end_nd_arc_temp,
                                              profile_i_rot[-1, 0]
                                              ) and profile_i_rot[-1, 0] != 1.:
                                    ps_end_nd_arc_temp = 1.
                                ps_end_nd_arc.append(ps_end_nd_arc_temp)
                            if inputs['layer_start_nd'][idx_sec, i] < loc_LE:
                                ps_start_nd_arc.append(0.)
                            else:
                                ps_start_nd_arc.append(
                                    float(
                                        spline_arc2xnd(
                                            inputs['layer_start_nd'][idx_sec,
                                                                     i])))
                else:
                    target_idx = inputs['layer_web'][idx_sec] - 1

                    if inputs['layer_thickness'][idx_sec, i] != 0.:
                        web_idx.append(idx_sec)

                        start_nd_arc = float(
                            spline_arc2xnd(
                                inputs['web_start_nd'][int(target_idx), i]))
                        end_nd_arc = float(
                            spline_arc2xnd(
                                inputs['web_end_nd'][int(target_idx), i]))

                        web_start_nd_arc.append(start_nd_arc)
                        web_end_nd_arc.append(end_nd_arc)

            # time1 = time.time() - time1
            # print(time1)

            # generate the Precomp composite stacks for chordwise regions
            if np.min([ss_start_nd_arc, ss_end_nd_arc]) < 0 or np.max(
                [ss_start_nd_arc, ss_end_nd_arc]) > 1:
                print('Error in the layer definition at station number ' +
                      str(i))
                exit()
            upperCS[i], region_loc_ss = region_stacking(
                i, ss_idx, ss_start_nd_arc, ss_end_nd_arc, layer_name,
                inputs['layer_thickness'][:,
                                          i], inputs['fiber_orientation'][:,
                                                                          i],
                layer_mat, material_dict, materials, region_loc_ss)
            lowerCS[i], region_loc_ps = region_stacking(
                i, ps_idx, ps_start_nd_arc, ps_end_nd_arc, layer_name,
                inputs['layer_thickness'][:,
                                          i], inputs['fiber_orientation'][:,
                                                                          i],
                layer_mat, material_dict, materials, region_loc_ps)
            if len(web_idx) > 0 or flatback:
                websCS[i] = web_stacking(i, web_idx, web_start_nd_arc,
                                         web_end_nd_arc,
                                         inputs['layer_thickness'][:, i],
                                         inputs['fiber_orientation'][:, i],
                                         layer_mat, material_dict, materials,
                                         flatback, upperCS[i])
            else:
                websCS[i] = CompositeSection([], [], [], [], [], [])

        sector_idx_strain_spar_cap_ss = [
            None if regs == None else regs[int(len(regs) / 2)]
            for regs in region_loc_ss[self.spar_cap_ss_var]
        ]
        sector_idx_strain_spar_cap_ps = [
            None if regs == None else regs[int(len(regs) / 2)]
            for regs in region_loc_ps[self.spar_cap_ps_var]
        ]
        sector_idx_strain_te_ss = [
            None if regs == None else regs[int(len(regs) / 2)]
            for regs in region_loc_ss[self.te_ss_var]
        ]
        sector_idx_strain_te_ps = [
            None if regs == None else regs[int(len(regs) / 2)]
            for regs in region_loc_ps[self.te_ps_var]
        ]

        # Get Beam Properties
        beam = PreComp(inputs['r'], inputs['chord'], np.zeros_like(
            inputs['r']), inputs['pitch_axis'], inputs['precurve'],
                       inputs['presweep'], profile, materials, upperCS,
                       lowerCS, websCS, sector_idx_strain_spar_cap_ps,
                       sector_idx_strain_spar_cap_ss, sector_idx_strain_te_ps,
                       sector_idx_strain_te_ss)
        EIxx, EIyy, GJ, EA, EIxy, x_ec, y_ec, rhoA, area, rhoJ, Tw_iner, flap_iner, edge_iner, x_tc, y_tc, x_sc, y_sc, x_cg, y_cg = beam.sectionProperties(
        )

        # outputs['eps_crit_spar'] = beam.panelBucklingStrain(sector_idx_strain_spar_cap_ss)
        # outputs['eps_crit_te'] = beam.panelBucklingStrain(sector_idx_strain_te_ss)

        xu_strain_spar, xl_strain_spar, yu_strain_spar, yl_strain_spar = beam.criticalStrainLocations(
            sector_idx_strain_spar_cap_ss, sector_idx_strain_spar_cap_ps)
        xu_strain_te, xl_strain_te, yu_strain_te, yl_strain_te = beam.criticalStrainLocations(
            sector_idx_strain_te_ss, sector_idx_strain_te_ps)

        # Store what materials make up the composites for SC/TE
        for i in range(self.n_span):
            for j in range(self.n_mat):
                if sector_idx_strain_spar_cap_ss[i]:
                    if j in upperCS[i].mat_idx[
                            sector_idx_strain_spar_cap_ss[i]]:
                        outputs['sc_ss_mats'][i, j] = 1.
                if sector_idx_strain_spar_cap_ps[i]:
                    if j in lowerCS[i].mat_idx[
                            sector_idx_strain_spar_cap_ps[i]]:
                        outputs['sc_ps_mats'][i, j] = 1.
                if sector_idx_strain_te_ss[i]:
                    if j in upperCS[i].mat_idx[sector_idx_strain_te_ss[i]]:
                        outputs['te_ss_mats'][i, j] = 1.
                if sector_idx_strain_te_ps[i]:
                    if j in lowerCS[i].mat_idx[sector_idx_strain_te_ps[i]]:
                        outputs['te_ps_mats'][i, j] = 1.

        outputs['z'] = inputs['r']
        outputs['EIxx'] = EIxx
        outputs['EIyy'] = EIyy
        outputs['GJ'] = GJ
        outputs['EA'] = EA
        outputs['EIxy'] = EIxy
        outputs['x_ec'] = x_ec
        outputs['y_ec'] = y_ec
        outputs['rhoA'] = rhoA
        outputs['A'] = area
        outputs['rhoJ'] = rhoJ
        outputs['Tw_iner'] = Tw_iner
        outputs['flap_iner'] = flap_iner
        outputs['edge_iner'] = edge_iner

        outputs["x_tc"] = x_tc
        outputs["y_tc"] = y_tc
        outputs["x_sc"] = x_sc
        outputs["y_sc"] = y_sc
        outputs["x_cg"] = x_cg
        outputs["y_cg"] = y_cg

        outputs['xu_strain_spar'] = xu_strain_spar
        outputs['xl_strain_spar'] = xl_strain_spar
        outputs['yu_strain_spar'] = yu_strain_spar
        outputs['yl_strain_spar'] = yl_strain_spar
        outputs['xu_strain_te'] = xu_strain_te
        outputs['xl_strain_te'] = xl_strain_te
        outputs['yu_strain_te'] = yu_strain_te
        outputs['yl_strain_te'] = yl_strain_te

        # Compute mass and inertia of blade and rotor
        blade_mass = np.trapz(rhoA, inputs['r'])
        blade_moment_of_inertia = np.trapz(rhoA * inputs['r']**2., inputs['r'])
        tilt = inputs['uptilt']
        n_blades = discrete_inputs['n_blades']
        mass_all_blades = n_blades * blade_mass
        Ibeam = n_blades * blade_moment_of_inertia
        Ixx = Ibeam
        Iyy = Ibeam / 2.0  # azimuthal average for 2 blades, exact for 3+
        Izz = Ibeam / 2.0
        Ixy = 0.0
        Ixz = 0.0
        Iyz = 0.0  # azimuthal average for 2 blades, exact for 3+
        # rotate to yaw c.s.
        I = DirectionVector(Ixx, Iyy, Izz).hubToYaw(
            tilt)  # because off-diagonal components are all zero
        I_all_blades = np.r_[I.x, I.y, I.z, Ixy, Ixz, Iyz]

        outputs['blade_mass'] = blade_mass
        outputs['blade_moment_of_inertia'] = blade_moment_of_inertia
        outputs['mass_all_blades'] = mass_all_blades
        outputs['I_all_blades'] = I_all_blades

        # Placeholder - rotor cost
        bcm = blade_cost_model(verbosity=self.verbosity)
        bcm.name = ''
        bcm.materials = {}
        bcm.mat_options = {}

        bcm.mat_options['core_mat_id'] = np.zeros(self.n_mat)
        bcm.mat_options['coating_mat_id'] = -1
        bcm.mat_options['le_reinf_mat_id'] = -1
        bcm.mat_options['te_reinf_mat_id'] = -1

        for i_mat in range(self.n_mat):
            name = discrete_inputs['mat_name'][i_mat]
            # if name != 'resin':
            bcm.materials[name] = {}
            bcm.materials[name]['id'] = i_mat + 1
            bcm.materials[name]['name'] = discrete_inputs['mat_name'][i_mat]
            bcm.materials[name]['density'] = inputs['rho'][i_mat]
            bcm.materials[name]['unit_cost'] = inputs['unit_cost'][i_mat]
            bcm.materials[name]['waste'] = inputs['waste'][i_mat] * 100.
            if discrete_inputs['component_id'][i_mat] > 1:  # It's a composite
                bcm.materials[name]['fiber_density'] = inputs['rho_fiber'][
                    i_mat]
                bcm.materials[name]['area_density_dry'] = inputs[
                    'rho_area_dry'][i_mat]
                bcm.materials[name]['fvf'] = inputs['fvf'][i_mat] * 100.
                bcm.materials[name]['fwf'] = inputs['fwf'][i_mat] * 100.
                bcm.materials[name]['ply_t'] = inputs['ply_t'][i_mat]
                if discrete_inputs['component_id'][
                        i_mat] > 3:  # The material does not need to be cut@station
                    bcm.materials[name]['cut@station'] = 'N'
                else:
                    bcm.materials[name]['cut@station'] = 'Y'
                    bcm.materials[name]['roll_mass'] = inputs['roll_mass'][
                        i_mat]
            else:
                bcm.materials[name]['fvf'] = 100.
                bcm.materials[name]['fwf'] = 100.
                bcm.materials[name]['cut@station'] = 'N'
                if discrete_inputs['component_id'][i_mat] <= 0:
                    bcm.materials[name]['ply_t'] = inputs['ply_t'][i_mat]

            if discrete_inputs['component_id'][i_mat] == 0:
                bcm.mat_options['coating_mat_id'] = bcm.materials[name][
                    'id']  # Assigning the material to the coating
            elif discrete_inputs['component_id'][i_mat] == 1:
                bcm.mat_options['core_mat_id'][
                    bcm.materials[name]['id'] -
                    1] = 1  # Assigning the material to the core
            elif discrete_inputs['component_id'][i_mat] == 2:
                bcm.mat_options['skin_mat_id'] = bcm.materials[name][
                    'id']  # Assigning the material to the shell skin
            elif discrete_inputs['component_id'][i_mat] == 3:
                bcm.mat_options['skinwebs_mat_id'] = bcm.materials[name][
                    'id']  # Assigning the material to the webs skin
            elif discrete_inputs['component_id'][i_mat] == 4:
                bcm.mat_options['sc_mat_id'] = bcm.materials[name][
                    'id']  # Assigning the material to the spar caps
            elif discrete_inputs['component_id'][i_mat] == 5:
                bcm.mat_options['le_reinf_mat_id'] = bcm.materials[name][
                    'id']  # Assigning the material to the le reinf
                bcm.mat_options['te_reinf_mat_id'] = bcm.materials[name][
                    'id']  # Assigning the material to the te reinf

        bcm.upperCS = upperCS
        bcm.lowerCS = lowerCS
        bcm.websCS = websCS
        bcm.profile = profile
        bcm.chord = inputs['chord']
        bcm.r = inputs['r'] - inputs['r'][0]
        bcm.bladeLength = inputs['r'][-1] - inputs['r'][0]
        bcm.le_location = inputs['pitch_axis']
        blade_cost, blade_mass = bcm.execute_blade_cost_model()

        outputs['total_blade_cost'] = blade_cost
        outputs['total_blade_mass'] = blade_mass
Ejemplo n.º 4
0
    def compute(self, inputs, outputs):

        PBEAM = False
        
        # Unpack inputs
        x_ref = inputs['blade_ref_axis'][:,0] # from PS to SS
        y_ref = inputs['blade_ref_axis'][:,1] # from LE to TE
        z_ref = inputs['blade_ref_axis'][:,2] # from root to tip
        r     = util.arc_length(inputs['blade_ref_axis'])
        blade_length = r[-1]
        theta = inputs['theta']
        chord = inputs['chord']
        x_sc  = inputs['x_sc']
        y_sc  = inputs['y_sc']
        A     = inputs['A']
        rhoA  = inputs['rhoA']
        rhoJ  = inputs['rhoJ']
        GJ    = inputs['GJ']
        EA    = inputs['EA']
        EIxx  = inputs['EIxx'] # edge (rotation about x)
        EIyy  = inputs['EIyy'] # flap (rotation about y)
        EIxy  = inputs['EIxy']
        lateral_clearance = 0.5*inputs['lateral_clearance'][0]
        vertical_clearance = inputs['vertical_clearance'][0]
        max_strains       = inputs['max_strains'][0]
        max_rot = inputs['max_root_rot_deg'][0]
        max_LV            = inputs['max_LV'][0]
        mass_car_4axle    = inputs['max_flatcar_weight_4axle'][0]
        mass_car_8axle    = inputs['max_flatcar_weight_8axle'][0]
        flatcar_tc_length = inputs['flatcar_tc_length'][0]


        #------- Get turn radius geometry for horizontal and vertical curves
        # Horizontal turns- defined as a degree of arc assuming a 100ft "chord"
        # https://trn.trains.com/railroads/ask-trains/2011/01/measuring-track-curvature
        angleH_rad = np.deg2rad(inputs['horizontal_angle_deg'][0])
        r_curveH   = spc.foot * 100. /(2.*np.sin(0.5*angleH_rad))
        arcsH      = r / r_curveH
        
        # Vertical curves on hills and sags defined directly by radius
        r_curveV   = inputs['min_vertical_radius'][0]
        arcsV      = r / r_curveV
        # ----------


        #---------- Put airfoil cross sections into principle axes
        # Determine principal C.S. (with swap of x, y for profile c.s.)
        EIxx_cs , EIyy_cs = EIyy.copy() , EIxx.copy()
        x_sc_cs , y_sc_cs = y_sc.copy() , x_sc.copy()
        EIxy_cs = EIxy.copy()

        # translate to elastic center
        EIxx_cs -= y_sc_cs**2 * EA
        EIyy_cs -= x_sc_cs**2 * EA
        EIxy_cs -= x_sc_cs * y_sc_cs * EA

        # get rotation angle
        alpha = 0.5*np.arctan2(2*EIxy_cs, EIyy_cs-EIxx_cs)

        # get moments and positions in principal axes
        EI11 = EIxx_cs - EIxy_cs*np.tan(alpha)
        EI22 = EIyy_cs + EIxy_cs*np.tan(alpha)
        ca   = np.cos(alpha)
        sa   = np.sin(alpha)
        def rotate(x,y):
            x2 =  x*ca + y*sa
            y2 = -x*sa + y*ca
            return x2, y2

        # Now store alpha for later use in degrees
        alpha = np.rad2deg(alpha)
        # -------------------

        
        # ---------- Frame3dd blade prep
        # Nodes: Prep data, but node x,y,z will shift for vertical and horizontal curves
        rad   = np.zeros(self.n_span) # 'radius' of rigidity at node- set to zero
        inode = 1+np.arange(self.n_span) # Node numbers (will convert to 1-based indexing later)
        L     = np.diff(r)

        # Reactions: attachment at root
        ireact    = np.array([inode[0]])
        rigid     = 1e16
        pinned    = rigid*np.ones(1)
        reactions = pyframe3dd.ReactionData(ireact, pinned, pinned, pinned, pinned, pinned, pinned, float(rigid))
        
        # Element data
        ielem = np.arange(1, self.n_span) # Element Numbers
        N1    = np.arange(1, self.n_span) # Node number start
        N2    = np.arange(2, self.n_span+1) # Node number finish
        E     = EA   / A
        rho   = rhoA / A
        J     = rhoJ / rho
        G     = GJ   / J
        Ix    = EIyy / E if PBEAM else EI11 / E
        Iy    = EIxx / E if PBEAM else EI22 / E
        Asx   = Asy = 1e-6*np.ones(ielem.shape) # Unused when shear=False

        # Have to convert nodal values to find average at center of element
        Abar,_   = util.nodal2sectional(A)
        Ebar,_   = util.nodal2sectional(E)
        rhobar,_ = util.nodal2sectional(rho)
        Jbar,_   = util.nodal2sectional(J)
        Gbar,_   = util.nodal2sectional(G)
        Ixbar,_  = util.nodal2sectional(Ix)
        Iybar,_  = util.nodal2sectional(Iy)

        # Angle of element principal axes relative to global coordinate system
        # Global c.s. is blade with z from root to tip, y from ss to ps, and x from LE to TE (TE points up)
        # Local element c.s. is airfoil (twist + principle rotation)
        if PBEAM:
            roll = np.zeros(theta.shape)
        else:
            roll,_ = util.nodal2sectional(theta + alpha)

        elements = pyframe3dd.ElementData(ielem, N1, N2, Abar, Asx, Asy, Jbar, Ixbar, Iybar, Ebar, Gbar, roll, rhobar)

        # Frame3dd options: no need for shear, axial stiffening, or higher resolution force calculations
        options = pyframe3dd.Options(False, False, -1)
        #-----------

        
        #------ Airfoil positions at which to measure strain
        # Find the cross sectional points furthest from the elastic center at each spanwise location to be used for strain measurement
        xps = np.zeros(self.n_span)
        xss = np.zeros(self.n_span)
        yps = np.zeros(self.n_span)
        yss = np.zeros(self.n_span)
        xle = np.zeros(self.n_span)
        xte = np.zeros(self.n_span)
        yle = np.zeros(self.n_span)
        yte = np.zeros(self.n_span)

        for i in range(self.n_span):        
            ## Rotate the profiles to the blade reference system
            profile_i = inputs['coord_xy_interp'][i,:,:]
            profile_i_rot = np.column_stack(util.rotate(inputs['pitch_axis'][i], 0., profile_i[:,0], profile_i[:,1], np.radians(theta[i])))
            # normalize
            profile_i_rot[:,0] -= min(profile_i_rot[:,0])
            profile_i_rot = profile_i_rot/ max(profile_i_rot[:,0])
            profile_i_rot_precomp = profile_i_rot.copy()
            idx_s = 0
            idx_le_precomp = np.argmax(profile_i_rot_precomp[:,0])
            if idx_le_precomp != 0:
                if profile_i_rot_precomp[0,0] == profile_i_rot_precomp[-1,0]:
                     idx_s = 1
                profile_i_rot_precomp = np.row_stack((profile_i_rot_precomp[idx_le_precomp:], profile_i_rot_precomp[idx_s:idx_le_precomp,:]))
            profile_i_rot_precomp[:,1] -= profile_i_rot_precomp[np.argmin(profile_i_rot_precomp[:,0]),1]

            # # renormalize
            profile_i_rot_precomp[:,0] -= min(profile_i_rot_precomp[:,0])
            profile_i_rot_precomp = profile_i_rot_precomp/ max(profile_i_rot_precomp[:,0])

            if profile_i_rot_precomp[-1,0] != 1.:
                profile_i_rot_precomp = np.row_stack((profile_i_rot_precomp, profile_i_rot_precomp[0,:]))

            # 'web' at trailing edge needed for flatback airfoils
            if profile_i_rot_precomp[0,1] != profile_i_rot_precomp[-1,1] and profile_i_rot_precomp[0,0] == profile_i_rot_precomp[-1,0]:
                flatback = True
            else:
                flatback = False

            xnode          = profile_i_rot_precomp[:,0]
            xnode_pa       = xnode - inputs['pitch_axis'][i]
            ynode          = profile_i_rot_precomp[:,1]
            theta_rad      = theta[i] * np.pi / 180.

            xnode_no_theta = xnode_pa * np.cos(-theta_rad) - ynode * np.sin(-theta_rad)
            ynode_no_theta = xnode_pa * np.sin(-theta_rad) + ynode * np.cos(-theta_rad)

            xnode_dim_no_theta = xnode_no_theta * chord[i]
            ynode_dim_no_theta = ynode_no_theta * chord[i]

            xnode_dim = xnode_dim_no_theta * np.cos(theta_rad) - ynode_dim_no_theta * np.sin(theta_rad)
            ynode_dim = xnode_dim_no_theta * np.sin(theta_rad) + ynode_dim_no_theta * np.cos(theta_rad)

            yss[i] = max(ynode_dim) - y_sc_cs[i]
            yps[i] = y_sc_cs[i] - min(ynode_dim)
            xte[i] = max(xnode_dim) - x_sc_cs[i]
            xle[i] = x_sc_cs[i] - min(xnode_dim)

        # Put these sectional points in airfoil principle directions
        xps_cs, yps_cs = yps, xps
        xss_cs, yss_cs = yss, xss
        
        ps1, ps2 = rotate(xps_cs, yps_cs)
        ss1, ss2 = rotate(xss_cs, yss_cs)

        xle_cs, yle_cs = yle, xle
        xte_cs, yte_cs = yte, xte
        
        le1, le2 = rotate(xle_cs, yle_cs)
        te1, te2 = rotate(xte_cs, yte_cs)
        #----------------


        #-------- Horizontal curve where we select blade support nodes on flat cars
        # Gravity field orientation
        gy = -gravity
        gx = gz = 0.0

        # Set clearance boundary
        r_envelopeH = r_curveH + lateral_clearance*np.array([-1, 1])

        # Use blade shape and clearance envelope to determine node position limits
        r_envelopeH_inner1 = r_envelopeH.min() + yss
        r_envelopeH_inner2 = r_envelopeH.min() + yps
        
        r_envelopeH_outer1 = r_envelopeH.max() - yps
        r_envelopeH_outer2 = r_envelopeH.max() - yss
            
        # Find rotation angles that keep blade within inner boundary
        # Function that does the structural analysis to be called during optimization
        def rotate_blade(ang):
            
            # Node location starting points when curving towards SS
            # (towards the LEFT with LE pointed down and standing at the root looking at tip)
            x_rot1, z_rot1 = util.rotate(r_curveH, 0.0, r_curveH + x_ref, z_ref, ang[0])

            # Node location starting points when curving towards PS
            # (towards the RIGHT with LE pointed down and standing at the root looking at tip)
            x_rot2, z_rot2 = util.rotate(-r_curveH, 0.0, -r_curveH + x_ref, z_ref, ang[1])

            # Check solved blade shape against envelope
            r_check1 = np.sqrt( x_rot1**2 + z_rot1**2)
            r_check2 = np.sqrt( x_rot2**2 + z_rot2**2)

            # Formulate as constraints for SLSQP
            cboundary = (np.sum( np.maximum(r_envelopeH_inner1 - r_check1, 0.0) ) + 
                         np.sum( np.maximum(r_envelopeH_inner2 - r_check2, 0.0) ) )

            return -cboundary

        # Initiliaze scipy minimization to find the right angle- biggest deflection that keeps blade away from inner envelope
        const         = {}
        const['type'] = 'ineq'
        const['fun']  = rotate_blade
        
        bounds = [np.deg2rad(max_rot)*np.r_[0,1], np.deg2rad(max_rot)*np.r_[-1,0]]
        x0     = np.deg2rad( max_rot*np.array([1.0, -1.0]) )
        result = minimize(lambda x: -np.sum(np.abs(x)), x0, method='slsqp', bounds=bounds, tol=1e-3, constraints=const)

        # Now rotate blade at optimized angle

        # Node location starting points when curving towards SS
        # (towards the LEFT with LE pointed down and standing at the root looking at tip)
        x_rot1, z_rot1 = util.rotate(r_curveH, 0.0, r_curveH + x_ref, z_ref, result.x[0])
        nodes1 = pyframe3dd.NodeData(inode, x_rot1, y_ref, z_rot1, rad)
        
        # Node location starting points when curving towards PS
        # (towards the RIGHT with LE pointed down and standing at the root looking at tip)
        x_rot2, z_rot2 = util.rotate(-r_curveH, 0.0, -r_curveH + x_ref, z_ref, result.x[1])
        nodes2 = pyframe3dd.NodeData(inode, x_rot2, y_ref, z_rot2, rad)
        
        # Initialize Frame3dd objects
        blade1 = pyframe3dd.Frame(nodes1, reactions, elements, options)
        blade2 = pyframe3dd.Frame(nodes2, reactions, elements, options)
        
        # Tolerance for envelop compliance checking
        tol = 3e-1
        
        # Function that does the structural analysis to be called during optimization
        def run_hcurve(FrIn, optFlag=True):
            Fr = FrIn.reshape((self.n_span-1, 2))
            
            # Will only worry about radial loads
            Fy = Mx = My = Mz = np.zeros(self.n_span-1)

            # Output containers
            RF_derailH = np.zeros(2) # Derailment reaction force
            r_check    = np.zeros((self.n_span, 2)) # Envelope constraint
            strainPS   = np.zeros((self.n_span, 2)) 
            strainSS   = np.zeros((self.n_span, 2))

            # Look over bend to PS/SS cases
            for k in range(2):
                if k == 0:
                    blade, r_outer, angs = blade1, r_envelopeH_outer1, arcsH[1:]
                else:
                    blade, r_outer, angs = blade2, r_envelopeH_outer2, np.pi-arcsH[1:]
                    
                # Load case: gravity + blade bending to conform to outer boundary
                load = pyframe3dd.StaticLoadCase(gx, gy, gz)

                # Put radial loads in x,z plane   
                Fx = -1e4*Fr[:,k]*np.cos(angs)
                Fz = -1e4*Fr[:,k]*np.sin(angs)
                load.changePointLoads(inode[1:], Fx, Fy, Fz, Mx, My, Mz)

                # Add load case to Frame3DD objects and run
                blade.clearLoadCases()
                blade.addLoadCase(load)

                # Run the case
                displacements, forces, forces_rxn, internalForces, mass, modal = blade.run()

                # Check solved blade shape against envelope
                r_check[:,k]  = np.sqrt( (blade.nx+displacements.dx[0,:])**2 + (blade.nz+displacements.dz[0,:])**2) - r_outer

                # Derailing reaction force on root node
                #  - Lateral force on wheels (multiply by 0.5 for 2 wheel sets)
                #  - Moment around axis perpendicular to ground
                RF_derailH[k] = 0.5*np.sqrt(forces_rxn.Fx**2 + forces_rxn.Fz**2) + np.abs(forces_rxn.Myy)/flatcar_tc_length

                # Element shear and bending, one per element, which are already in principle directions in Hansen's notation
                # Zero-ing out axial stress as there shouldn't be any for pure beam bending
                Fz = np.r_[-forces.Nx[ 0,0],  forces.Nx[ 0, 1::2]]
                M1 = np.r_[-forces.Myy[0,0],  forces.Myy[0, 1::2]]
                M2 = np.r_[ forces.Mzz[0,0], -forces.Mzz[0, 1::2]]
                if PBEAM: M1,M2 = rotate(M1,M2)
                
                # Compute strain at the two points: pressure/suction side extremes
                strainPS[:,k] = -(M1/EI11*ps2 - M2/EI22*ps1 + Fz/EA)  # negative sign because Hansen c3 is opposite of Precomp z
                strainSS[:,k] = -(M1/EI11*ss2 - M2/EI22*ss1 + Fz/EA)

            if optFlag:
                # First constraint is compliance with outer boundary
                cboundary = np.maximum(r_check, 0)

                # Second constraint is reaction forces for derailment:
                crxn = np.maximum(RF_derailH - (0.5 * mass_car_8axle * gravity)/max_LV, 0.0)

                # Third constraint is keeping the strains reasonable
                cstrainPS = np.maximum(np.abs(strainPS) - max_strains, 0.0)
                cstrainSS = np.maximum(np.abs(strainSS) - max_strains, 0.0)

                # Accumulate constraints
                cons = np.array([np.sum(cboundary) , np.sum(crxn) , np.sum(cstrainPS) , np.sum(cstrainSS)])
            
                return -cons
            else:
                return RF_derailH, strainPS, strainSS

        # Initiliaze scipy minimization: Minimize force applied that keeps blade within all constraints
        const         = {}
        const['type'] = 'ineq'
        const['fun']  = run_hcurve

        npts   = 2*(self.n_span-1)
        bounds = [(0.0, 1e2)]*npts
        x0     = 1e2*np.ones(npts)
        result = minimize(lambda x: np.sum(np.abs(x)), x0, method='slsqp', bounds=bounds,
                          tol=1e-3, constraints=const, options={'maxiter': 100} )

        # if result.success:
        # Evaluate optimized solution
        RF_derailH, strainPS, strainSS = run_hcurve(result.x, optFlag=False)

        # Express derailing force as a constraint
        outputs['constr_LV_4axle_horiz'] = RF_derailH / (0.5 * mass_car_4axle * gravity) / max_LV
        outputs['constr_LV_8axle_horiz'] = RF_derailH / (0.5 * mass_car_8axle * gravity) / max_LV

        # Strain constraint outputs
        outputs['constr_strainPS'] = np.max(np.abs(strainPS) / max_strains, axis = 1)
        outputs['constr_strainSS'] = np.max(np.abs(strainSS) / max_strains, axis = 1)
        # else:
        #     outputs['LV_constraint_4axle_horiz'] = 2.
        #     outputs['LV_constraint_8axle_horiz'] = 2.
        #     outputs['constr_strainPS']           = 2. * np.ones([npts,2])
        #     outputs['constr_strainSS']           = 2. * np.ones([npts,2])
        #     print('The optimization cannot satisfy the blade rail transport constraints.')

        
        '''