def testTypeChecks(self): stdout = test_util.StdoutPatch() # Double array. with stdout, self.assertRaises(AssertionError): mconfig.MakeParams( 'common.all_params', overrides={ 'control': { 'control_output': { 'flaps_min': [0] + [0.0 for _ in range(labels.kNumFlaps - 1)] } } }, override_method='derived') self.assertRegexpMatches( stdout.Read(), r'(?s).*control_output\.flaps_min\[0\] must be a float.*') # Vec3. with stdout, self.assertRaises(AssertionError): mconfig.MakeParams('common.all_params', overrides={ 'sim': { 'ground_frame_sim': { 'pos_ecef': [1e3, 1, 1e1] } } }, override_method='derived') self.assertRegexpMatches( stdout.Read(), r'(?s).*ground_frame_sim\.pos_ecef\[1\] must be a float.*') # Integer array. with stdout, self.assertRaises(AssertionError): mconfig.MakeParams('common.all_params', overrides={ 'control': { 'fault_detection': { 'gsg': { 'no_update_counts_limit': [0, 3.14] } } } }, override_method='derived') self.assertRegexpMatches( stdout.Read(), (r'(?s).*fault_detection\.gsg\.no_update_counts_limit\[1\]' ' must be an int.*'))
def main(argv): tether_force_model = dynamics.ConstantTetherForceModel( np.matrix(np.zeros((3, 1)))) all_params = mconfig.MakeParams('common.all_params') wing = dynamics.Wing(all_params['system'], all_params['sim'], dynamics.SwigAeroModel(), tether_force_model) log_file = h5py.File(argv[1]) sim = log_file['messages']['kAioNodeSimulator'] sim_telem = sim['kMessageTypeSimTelemetry']['message'] controller_a = log_file['messages']['kAioNodeControllerA'] control_telem = controller_a['kMessageTypeControlDebug']['message'] wing_telem = sim_telem['wing'] control_idx = np.where(control_telem['flight_mode'] >= control_types.kFlightModeHoverFullLength)[0] assert control_idx.size > 0 sim_idx = np.where( sim_telem['time'] > control_telem['time'][control_idx[0]])[0] assert sim_idx.size > 0 for i in range(sim_idx[0], len(sim_telem)): _CompareTimeStep(tether_force_model, wing, wing_telem[i])
def testMixRotors(self): all_params = mconfig.MakeParams('common.all_params') rotors_0 = actuator_util.MixRotors( { 'thrust': 1e4, 'moment': [0.0, 0.0, 0.0] }, { 'thrust': 1e-3, 'moment': [1e-4, 1.0, 1.0] }, 0.0, [0.0, 0.0, 0.0], control_types.kStackingStateNormal, False, all_params['system']['phys']['rho'], all_params['system']['rotors'], all_params['control']['rotor_control']) self.assertTrue(isinstance(rotors_0, np.matrix)) self.assertEqual(control_types.kNumMotors, rotors_0.shape[0]) self.assertEqual(1, rotors_0.shape[1]) rotors_10 = actuator_util.MixRotors( { 'thrust': 1e4, 'moment': [0.0, 0.0, 0.0] }, { 'thrust': 1e-3, 'moment': [1e-4, 1.0, 1.0] }, 10.0, [0.0, 0.0, 0.0], control_types.kStackingStateNormal, False, all_params['system']['phys']['rho'], all_params['system']['rotors'], all_params['control']['rotor_control']) for i in range(rotors_10.shape[0]): self.assertGreaterEqual(rotors_10[i, 0], rotors_0[i, 0])
def testMakeParamsIntegerIndexedOverrides(self): overrides = { 'sim': { 'aero_sim': { 'force_coeff_w_scale_factors': { 'flap_derivatives': { 0: { '1': 22.0 }, 1: [2.0, 3.0, 4.0] } } } } } params = mconfig.MakeParams('common.all_params', overrides=overrides, override_method='derived') factors = params['sim']['aero_sim']['force_coeff_w_scale_factors'] for i in range(sim_types.kNumFlaps): if i == 1: self.assertEqual(2.0, factors['flap_derivatives'][i][0]) self.assertEqual(3.0, factors['flap_derivatives'][i][1]) self.assertEqual(4.0, factors['flap_derivatives'][i][2]) else: for j in range(3): if i == 0 and j == 1: self.assertEqual(22.0, factors['flap_derivatives'][i][j]) else: self.assertEqual(1.0, factors['flap_derivatives'][i][j])
def setUp(self): all_params = mconfig.MakeParams('common.all_params') self._trans_in = trans_in.ControlDesign(all_params['system'], all_params['control'], all_params['sim']) self._trans_in_frame = self._trans_in._trans_in_frame (self._trim_state, self._trim_inputs) = self._trans_in.CalcTrim( 5.0, np.deg2rad(50.0), False)
def __init__(self): iec_params = mconfig.MakeParams(FLAGS.wing_model + '.sim.iec_sim', None) sys_params = mconfig.MakeParams(FLAGS.wing_model + '.system_params') v_in = float(iec_params['v_in']) # Cut-in wind speed. v_out = float(iec_params['v_out']) # Cut-out wind speed. self._v_hub = float(iec_params['v_r1']) # Rated hub speed. # alpha is the wind shear exponent defined in IEC 61400-1:2005 on page 24. alpha = 0.2 # Take the hub height to be the tether length * sin(30 deg.) + height of # the GSG. z_hub = float(sys_params['tether']['length'] * numpy.sin(numpy.pi / 6.0) + sys_params['ground_frame']['ground_z']) # Height at which wind_speed is measured. # This assumes that the perch z-axis is parallel to the # g-coordinate z-axis are aligned. z_sensor = float(sys_params['ground_frame']['ground_z'] - sys_params['wind_sensor']['pos_parent'][2]) # v_hub * shear_correction = wind_speed. shear = float((z_sensor / z_hub)**alpha) # The *_sensed variables are the speed measured at the ground to # arrive at a given speed at the hub assuming the shear model # above. self._v_in_sensed = v_in * shear self._v_out_sensed = v_out * shear self._v_hub_sensed = self._v_hub * shear self._v_hub_m2_sensed = (self._v_hub - 2.0) * shear # m2 means minus two. self._v_hub_p2_sensed = (self._v_hub + 2.0) * shear # p2 means plus two. self._fault_power_sys_zero = { 't_start': 20.0, 't_end': 25.0, 'component': 'PowerSys/motor_connections[0]', 'type': sim_types.kSimFaultActuatorZero, }
def setUp(self): # Default parameters created when MakeParams is run on the # all_params.py file without overrides. self._default_params = mconfig.MakeParams('common.all_params') # Set flight plan to something that is not the default flight # plan. This makes the harmless assumption that there are at # least two flight plans 0 and 1. if self._default_params['system']['flight_plan'] == 0: self._changed_flight_plan = 1 else: self._changed_flight_plan = 0
def setUp(self): all_params = mconfig.MakeParams('common.all_params') self._sim_params = all_params['sim'] self._system_params = all_params['system'] self._dcm_g2control = geometry.AngleToDcm(0.1, 0.2, 0.3) motor_model = dynamics.PureForceMomentMotorModel( self._system_params['rotors'], self._system_params['wing']['center_of_mass_pos']) self._wing = dynamics.Wing( self._system_params, self._sim_params, VacuumAeroModel(), motor_model, dynamics.ConstantTetherForceModel(np.matrix(np.zeros((3, 1)))))
def GenerateConfigList(base, override_list): """Generate a series of configs based on lists of overrides.""" override_list = _PreprocessOverrideList(override_list) clean_overrides = [o for o in override_list if o.name_def.clean] unclean_overrides = [o for o in override_list if not o.name_def.clean] if not unclean_overrides: base_config = mconfig.MakeParams('common.all_params', copy.deepcopy(base), override_method='derived') for i in range(len(override_list[0].values)): if unclean_overrides: base_override = copy.deepcopy(base) for override in unclean_overrides: ApplyOverride(base_override, override.name_def, override.values[i], True) config = mconfig.MakeParams('common.all_params', base_override, override_method='derived') else: config = copy.deepcopy(base_config) for override in clean_overrides: ApplyOverride(config, override.name_def, override.values[i]) config['adjusted_parameters'] = { o.name_def.name: o.values[i] for o in override_list} yield config
def RunFlightChecks(log_file): """Run flight checks on an H5 log file.""" system_params = log_file['parameters']['system_params'] simulator = log_file['messages']['kAioNodeSimulator'] sim_telem = simulator['kMessageTypeSimTelemetry']['message'] controller = log_file['messages']['kAioNodeControllerA'] control_telem = controller['kMessageTypeControlDebug']['message'] monitor_params = mconfig.MakeParams('common.monitor.monitor_params') CheckOverTension(monitor_params, sim_telem) CheckMinAltitude(system_params, sim_telem) CheckFlightModeProgression(control_telem) CheckCrosswindMinAltitude(system_params, control_telem, sim_telem)
def testAllWingSerials(self): # TODO: OktKite. When the Oktoberkite has a valid yaml system_config # entry remove the restriction which models are run. for wing_serial in _WING_SERIAL_HELPER.Values(): if (system_types.WingSerialToModel(wing_serial) == system_types.kWingModelOktoberKite): continue is_active = mconfig.MakeParams( 'common.wing_serial_status', overrides={'wing_serial': wing_serial}, override_method='derived') if not is_active: continue config = system_config.SystemConfig.GetSystemConfigBySerial( wing_serial) self.assertIsNotNone(config)
def _GenerateConfigs(self): # Create a JSON file describing the simulation parameters for each # wind speed. We override the flight plan because we are only # interested in power production during crosswind flight. We # override the aerodynamic and other simulation parameters to make # the most realistic simulation of power production. for joystick_throttle in FLAGS.joystick_throttles: for v_wind in FLAGS.wind_speeds: overrides = { 'system': { 'flight_plan': sim_types.kFlightPlanStartDownwind }, 'sim': { 'joystick_sim': { 'num_updates': 1, 'updates': [{ 't_update': 0.0, 'type': sim_types.kSimJoystickUpdateThrottle, 'enum_value': sim_types.kSimJoystickThrottleManual, 'value': float(joystick_throttle), }] * sim_types.MAX_JOYSTICK_UPDATES }, # It is necessary to add the perch here so the winch # updates and payout is set correctly. This helps the # wing hover properly by setting the proper # feed-forward thrust when the simulation begins. 'sim_opt': (sim_types.kSimOptImperfectSensors | sim_types.kSimOptPerch | sim_types.kSimOptStackedPowerSystem), 'phys_sim': { 'wind_model': sim_types.kWindModelDrydenTurbulence, 'wind_speed': float(v_wind), }, 'sim_time': FLAGS.sim_time } } yield mconfig.MakeParams('common.all_params', overrides, override_method='derived')
def CalcWindVelMwAndMeanWind(wind_vel, wind_source): """Converts wind to the mean wind frame, and provides mean speed/direction. Args: wind_vel: Wind velocity [m/s] in the wind sensor frame or the ground frame, depending on wind_source (ws and est, respectively). wind_source: 'est' or 'ws' Returns: (wind_vel_mw, mean_wind_speed, mean_wind_dir), defined as: wind_vel_mw: Wind velocity [m/s] in the mean wind frame. mean_wind_speed: Mean wind speed [m/s] in the horizontal plane. mean_wind_dir: Direction [rad] of the mean wind. Raises: ValueError: The specified wind source was invalid. """ # Transform from weather station to ground coordinates, if needed if wind_source == 'ws': system_params = mconfig.MakeParams('m600.system_params') assert ( system_params['gs_model'] == system_types.kGroundStationModelTopHat ), ('This script assumes a top hat configuration.') dcm_ws2g = np.array( system_params['wind_sensor']['dcm_parent2ws']['d']).T wind_vel_g = np.dot(dcm_ws2g, wind_vel.T).T elif wind_source == 'est': wind_vel_g = wind_vel else: raise ValueError('Invalid value of --wind_source: %s.' % wind_source) # Calculate mean wind xy-speed and direction. mean_wind_g = np.mean(wind_vel_g, axis=0) mean_wind_speed = np.hypot(mean_wind_g[0], mean_wind_g[1]) mean_wind_dir = np.arctan2(-mean_wind_g[1], -mean_wind_g[0]) # Rotate wind velocity to the mean wind frame. dcm_mw2g = wind_frame.Mat3() wind_frame.CalcDcmMwToG(mean_wind_dir, ctypes.byref(dcm_mw2g)) dcm_mw2g = np.array([row[:] for row in dcm_mw2g.d]) wind_vel_mw = np.dot(dcm_mw2g.T, wind_vel_g.T).T return wind_vel_mw, mean_wind_speed, mean_wind_dir
def testLinearizeMixRotors(self): params = mconfig.MakeParams('common.all_params') a = actuator_util.LinearizeMixRotors( { 'thrust': 1e4, 'moment': [0.0, 0.0, 0.0] }, params) # A pure thrust command results in almost equal rotor speed increase for # all the rotors. self.assertTrue(all(np.abs(a[:, 0] / a[0, 0] - 1.0) < 5e-2)) # A pure pitch moment command results in almost equal rotor speed increase # for the top rotors, of opposite sign as the rotor speed increase of the # lower rotors. self.assertTrue( all( np.abs(a[:, 2] / a[0, 2] - np.matrix([1, 1, 1, 1, -1, -1, -1, -1]).T < 5e-2)))
def GenerateConfigPermutations(base, override_params): """Generate a series of configs based on permutations of overrides.""" override_params = _PreprocessOverrideList(override_params) clean_overrides = [o for o in override_params if o.name_def.clean] unclean_overrides = [o for o in override_params if not o.name_def.clean] for unclean_values in itertools.product( *[o.values for o in unclean_overrides]): base_override = copy.deepcopy(base) for override, value in zip(unclean_overrides, unclean_values): ApplyOverride(base_override, override.name_def, value, True) base_config = mconfig.MakeParams('common.all_params', base_override, override_method='derived') for clean_values in itertools.product(*[o.values for o in clean_overrides]): config = copy.deepcopy(base_config) for override, value in zip(clean_overrides, clean_values): ApplyOverride(config, override.name_def, value) adjusted_params = (zip(unclean_overrides, unclean_values) + zip(clean_overrides, clean_values)) config['adjusted_parameters'] = { o.name_def.name: v for o, v in adjusted_params} yield config
def testMakeParamsDerivedOverrides(self): params = mconfig.MakeParams('common.all_params', overrides={ 'control': { 'hover': { 'path': { 'reel_azimuth_offset': -0.2222, 'accel_start_elevation': 0.2222 } } }, 'sim': { 'ground_frame_sim': { 'pos_ecef': [1e3, 1e2, 1e1] } }, 'system': { 'flight_plan': self._changed_flight_plan } }, override_method='derived') # Check that wind speed is overridden as expected. self.assertEqual(params['sim']['ground_frame_sim']['pos_ecef'], [1e3, 1e2, 1e1]) # Check that multiple parameters in the same module are overridden. hover_path_params = params['control']['hover']['path'] self.assertEqual(hover_path_params['reel_azimuth_offset'], -0.2222) self.assertEqual(hover_path_params['accel_start_elevation'], 0.2222) # Check that the derived override overrides the main flight plan # and the derived flight plan used in the controller. self.assertEqual(params['system']['flight_plan'], self._changed_flight_plan) self.assertEqual(params['control']['flight_plan'], self._changed_flight_plan)
def testSystemServoFlapRatios(self): """Sanity check servo/flap ratios in system config (used for rudder).""" servo_config = mconfig.MakeParams( 'common.all_params')['system']['servos'] for servo in range(system_types.kNumServos): if servo == system_types.kServoR1 or servo == system_types.kServoR2: self.assertEqual( servo_config[servo]['linear_servo_to_flap_ratio'], 0.0) else: self.assertAlmostEqual( servo_config[servo]['linear_servo_to_flap_ratio'], 1.0, delta=_FLOAT_DELTA) self.assertEqual( servo_config[servo]['nonlinear_servo_to_flap_ratio'], 0.0) self.assertAlmostEqual(servo_config[ system_types.kServoR1]['nonlinear_servo_to_flap_ratio'], servo_config[system_types.kServoR2] ['nonlinear_servo_to_flap_ratio'], delta=_FLOAT_DELTA)
def GetParams(wing_model, wing_serial, use_wake_model=True): """Returns the set of parameters used for the model. Args: wing_model: Wing model (e.g. 'm600'). wing_serial: String giving the desired wing serial number (e.g. '01'). use_wake_model: Boolean flag that determines whether the advected rotor wake is included in the calculation of the local apparent wind. Returns: Parameter structure for the hover model. """ wing_config_name = wing_flag.WingModelToConfigName(wing_model) if wing_config_name == 'oktoberkite': wing_serial_helper = c_helpers.EnumHelper('WingSerialOktoberKite', system_types) db_name = 'avl/oktoberkite.avl' elif wing_config_name == 'm600': wing_serial_helper = c_helpers.EnumHelper('WingSerial', system_types) db_name = 'avl/m600_low_tail_no_winglets.avl' else: assert False, 'Wing model, %s, is not recognized.' % wing_config_name mconfig.WING_MODEL = wing_config_name system_params = mconfig.MakeParams( wing_config_name + '.system_params', overrides={'wing_serial': wing_serial_helper.Value(wing_serial)}, override_method='derived') # Thrust level is necessary for the propeller wake model. It is # determined by assuming the kite is supporting the full weight of # the tether and has to balance the pitching moment about the # C.G. with differential thrust between the top and bottom motor # rows: # # 4.0 * thrust_top + 4.0 * thrust_bot = weight # z_top * thrust_top + z_bot * thrust_bot = 0.0 total_mass = (system_params['wing']['m'] + (system_params['tether']['linear_density'] * system_params['tether']['length'])) weight = system_params['phys']['g'] * total_mass main_wing_incidence_deg = system_params['wing']['wing_i'] rotor_pos_z = [ rotor['pos'][2] - system_params['wing']['center_of_mass_pos'][2] for rotor in system_params['rotors'] ] z_bot = np.mean([z for z in rotor_pos_z if z > 0.0]) z_top = np.mean([z for z in rotor_pos_z if z < 0.0]) thrust_bot = weight / (4.0 * (1.0 + (-z_bot / z_top))) thrust_top = thrust_bot * (-z_bot / z_top) rotors = [{ 'pos': rotor['pos'], 'radius': rotor['D'] / 2.0, 'thrust': thrust_bot if rotor['pos'][2] > 0.0 else thrust_top } for rotor in system_params['rotors']] # It is assumed that all rotors have the same pitch angle. rotor_pitches = [ np.arctan2(-rotor['axis'][2], rotor['axis'][0]) for rotor in system_params['rotors'] ] assert np.all(rotor_pitches == rotor_pitches[0]) aero_home = os.path.join(makani.HOME, 'analysis/aero/') wing = avl_reader.AvlReader(os.path.join(aero_home, db_name)) # TODO: Extract airfoil information from avl_reader.py. if wing_config_name == 'm600': # The maximum value of 16.0 degrees for the M600 is because of the airfoil # hysteresis going from attached flow to detached flow, which occures during # HoverAccel. stall_angles_deg = [1.0, 16.0] elif wing_config_name == 'oktoberkite': # (b/146071300) Currently, there is no understanding of the hysteresis # curve. This needs to be understood and modified once available. # (b/146061441) Additional ramifications for this choice. stall_angles_deg = [1.0, 20.0] else: assert False, 'Unknown wing model.' # (b/146081917) Oktoberkite will utilize the same airfoils as the M600. outer_wing_airfoil = airfoil.Airfoil(os.path.join( aero_home, 'hover_model/airfoils/oref.json'), stall_angles_deg=stall_angles_deg) inner_wing_airfoil = airfoil.Airfoil(os.path.join( aero_home, 'hover_model/airfoils/ref.json'), stall_angles_deg=stall_angles_deg) pylon_airfoil = airfoil.Airfoil(os.path.join( aero_home, 'hover_model/airfoils/mp6d.json'), stall_angles_deg=[-10.0, 10.0]) airfoils = { 'Wing (panel 0)': outer_wing_airfoil, 'Wing (panel 1)': outer_wing_airfoil, 'Wing (panel 2)': inner_wing_airfoil, 'Wing (panel 3)': inner_wing_airfoil, 'Wing (panel 4)': inner_wing_airfoil, 'Wing (panel 5)': inner_wing_airfoil, 'Wing (panel 6)': inner_wing_airfoil, 'Wing (panel 7)': inner_wing_airfoil, 'Wing (panel 8)': outer_wing_airfoil, 'Wing (panel 9)': outer_wing_airfoil, 'Pylon 1': pylon_airfoil, 'Pylon 2': pylon_airfoil, 'Pylon 3': pylon_airfoil, 'Pylon 4': pylon_airfoil, 'Horizontal tail': airfoil.Airfoil(os.path.join(aero_home, 'hover_model/airfoils/f8.json'), stall_angles_deg=[-10.0, 10.0]), 'Vertical tail': airfoil.Airfoil(os.path.join( aero_home, 'hover_model/airfoils/approx_blade_rj8.json'), stall_angles_deg=[-10.0, 10.0]) } # TODO: Extract flap index information from # avl_reader.py. # The indices of the wing pannel indicate which flap number they are going to # be in the controller, i.e. "0" is A1 for the M600 and "7" is the rudder for # the M600. An index of -1 means that the panel has no flap section. if wing_config_name == 'm600': delta_indices = { 'Wing (panel 0)': 0, 'Wing (panel 1)': 1, 'Wing (panel 2)': -1, 'Wing (panel 3)': 2, 'Wing (panel 4)': -1, 'Wing (panel 5)': -1, 'Wing (panel 6)': 3, 'Wing (panel 7)': -1, 'Wing (panel 8)': 4, 'Wing (panel 9)': 5, 'Pylon 1': -1, 'Pylon 2': -1, 'Pylon 3': -1, 'Pylon 4': -1, 'Horizontal tail': 6, 'Vertical tail': 7 } elif wing_config_name == 'oktoberkite': delta_indices = { 'Wing (panel 0)': 0, 'Wing (panel 1)': 1, 'Wing (panel 2)': 2, 'Wing (panel 3)': -1, 'Wing (panel 4)': -1, 'Wing (panel 5)': -1, 'Wing (panel 6)': -1, 'Wing (panel 7)': 3, 'Wing (panel 8)': 4, 'Wing (panel 9)': 5, 'Pylon 1': -1, 'Pylon 2': -1, 'Pylon 3': -1, 'Pylon 4': -1, 'Horizontal tail': 6, 'Vertical tail': 7 } # TODO: Modify the hover model to accept the # avl_reader.py surface dictionary directly. hover_model_panels = [] for surface in wing.properties['surfaces']: if surface['name'] in ('Horizontal tail', 'Vertical tail', 'Pylon 1', 'Pylon 2', 'Pylon 3', 'Pylon 4'): hover_model_panels.append({ 'name': surface['name'], 'area': surface['area'], 'span': surface['span'], 'surface_span': surface['span'], 'chord': surface['standard_mean_chord'], 'aspect_ratio': surface['span']**2.0 / surface['area'], # The 2.0 is a fudge factor to match the C_Y slope from AVL. 'cl_weight': 2.0 if surface['name'][0:5] == 'Pylon' else 1.0, 'pos_b': surface['aerodynamic_center_b'], 'airfoil': airfoils[surface['name']], 'incidence': surface['mean_incidence'], # These surfaces are single panel. So there is no relative incidence. 'relative_incidence': 0.0, 'dcm_b2s': surface['dcm_b2surface'], 'delta_index': delta_indices[surface['name']] }) elif surface['name'] == 'Wing': for i, panel in enumerate(surface['panels']): panel_name = surface['name'] + ' (panel %d)' % i hover_model_panels.append({ 'name': panel_name, 'area': panel['area'], 'span': panel['span'], 'surface_span': surface['span'], 'chord': panel['standard_mean_chord'], 'aspect_ratio': surface['span']**2.0 / surface['area'], 'cl_weight': (surface['mean_incidence'] * surface['standard_mean_chord'] / (panel['mean_incidence'] * panel['standard_mean_chord'])), 'pos_b': panel['aerodynamic_center_b'], 'airfoil': airfoils[panel_name], 'incidence': panel['mean_incidence'], 'relative_incidence': (panel['mean_incidence'] - np.deg2rad(main_wing_incidence_deg)), 'dcm_b2s': surface['dcm_b2surface'], 'delta_index': delta_indices[panel_name] }) else: assert False # Check that we aren't missing any panels. assert (len(hover_model_panels) == len(delta_indices) and len(hover_model_panels) == len(airfoils)) return { 'wing_serial': wing_serial, 'git_commit': build_info.GetGitSha(), 'use_wake_model': use_wake_model, 'rotors': rotors, 'rotor_pitch': rotor_pitches[0], 'phys': { 'rho': 1.1, 'dynamic_viscosity': 1.789e-5 }, 'wing': { 'area': system_params['wing']['A'], 'b': system_params['wing']['b'], 'c': system_params['wing']['c'], 'wing_i': system_params['wing']['wing_i'] }, 'center_of_mass_pos': system_params['wing']['center_of_mass_pos'], 'panels': hover_model_panels }
def main(unused_argv): all_params = mconfig.MakeParams('common.all_params') trimmer = TrimSelector(all_params['system'], all_params['control'], all_params['sim']) trimmer.PrintTrim(*trimmer.CalcTrim())
# Convert a configuration filename to the equivalent module name by # remove the beginning "config/" and the trailing ".py", and then # replacing "/" with ".". module_str = FLAGS.input_file[7:-3].replace('/', '.') # Parse overrides command line option. if FLAGS.overrides: overrides = overrides_util.PreprocessOverrides( json.loads(FLAGS.overrides)) else: overrides = {} # Make a parameters dict from the configuration files. params = mconfig.MakeParams(module_str, overrides=overrides, override_method='derived') # Write parameters to the output_file or to stdout. if FLAGS.output_file is None: print params else: if FLAGS.type == 'json': WriteJsonParams(params, FLAGS.output_file) elif FLAGS.type == 'control': WriteDefaultControlParams(params, FLAGS.output_file) elif FLAGS.type == 'monitor': WriteDefaultMonitorParams(params, FLAGS.output_file) elif FLAGS.type == 'sim': WriteDefaultSimParams(params, FLAGS.output_file) elif FLAGS.type == 'system':
def testMakeParamsArrayOverrides(self): with self.assertRaises(mconfig.InvalidOverrideException): # There are either one or three IMUs so this call should always fail. params = mconfig.MakeParams( 'common.all_params', overrides={'sim': { 'wing_imus_sim': [{ 'delay': 0.22 }, {}] }}, override_method='derived') with self.assertRaises(mconfig.InvalidOverrideException): overrides = { 'sim': { 'aero_sim': { 'force_coeff_w_scale_factors': { 'flap_derivatives': { 's': { '1': 22.0 }, } } } } } params = mconfig.MakeParams('common.all_params', overrides=overrides, override_method='derived') with self.assertRaises(mconfig.InvalidOverrideException): overrides = { 'sim': { 'aero_sim': { 'force_coeff_w_scale_factors': { 'flap_derivatives': { 0: { '3': 22.0 }, } } } } } params = mconfig.MakeParams('common.all_params', overrides=overrides, override_method='derived') if sim_types.kNumWingImus == 1: params = mconfig.MakeParams( 'common.all_params', overrides={'sim': { 'wing_imus_sim': [{ 'delay': 0.22 }] }}, override_method='derived') # Check that delay is overridden as expected. self.assertEqual(params['sim']['wing_imus_sim'][0]['delay'], 0.22) else: params = mconfig.MakeParams('common.all_params', overrides={ 'sim': { 'wing_imus_sim': [{ 'delay': 0.2 }, {}, { 'delay': 2.0 }] } }, override_method='derived') # Check that delays are overridden as expected. self.assertEqual(params['sim']['wing_imus_sim'][0]['delay'], 0.2) self.assertEqual(params['sim']['wing_imus_sim'][2]['delay'], 2.0) # This delay should still have the default value. self.assertEqual( params['sim']['wing_imus_sim'][1]['delay'], self._default_params['sim']['wing_imus_sim'][1]['delay'])
def BuildConfig(self, case_name): """Build a config for a specific IEC case. Args: case_name: Name of the case. Returns: A config suitable for running a simulation corresponding to the provided case. Raises: ValueError: `case_name` does not correspond to a known case. """ overrides = { 'sim': { 'iec_sim': {}, 'phys_sim': { 'wind_model': sim_types.kWindModelIec, 'wind_shear_exponent': 0.2, }, 'sim_opt': (sim_types.kSimOptFaults | sim_types.kSimOptPerch | sim_types.kSimOptStackedPowerSystem | sim_types.kSimOptGroundContact | sim_types.kSimOptImperfectSensors), 'sim_time': 150.0, }, 'system': { 'flight_plan': sim_types.kFlightPlanStartDownwind } } iec_sim = overrides['sim']['iec_sim'] phys_sim = overrides['sim']['phys_sim'] # Power production # DLC 1.1: NTM V_in < V_hub < V_out, Ultimate # DLC 1.2: NTM V_in < V_hub < V_out, Fatigue if case_name in ('1.1', '1.2'): iec_sim['load_case'] = sim_types.kIecCaseNormalTurbulenceModel phys_sim['wind_speed'] = self._v_hub # DLC 1.3: ETM V_in < V_hub < V_out, Ultimate elif case_name == '1.3': iec_sim['load_case'] = sim_types.kIecCaseExtremeTurbulenceModel # DLC 1.4: ECD V_hub = V_r - 2, V_r, V_r + 2, Ultimate elif case_name.startswith('1.4'): iec_sim['event_t_start'] = 60.0 iec_sim['load_case'] = ( sim_types.kIecCaseExtremeCoherentGustWithDirectionChange) if case_name == '1.4b': phys_sim['wind_speed'] = self._v_hub_m2_sensed elif case_name == '1.4c': phys_sim['wind_speed'] = self._v_hub_p2_sensed # DLC 1.5: EWS V_in < V_hub < V_out, Ultimate elif case_name == '1.5a': iec_sim['event_t_start'] = 20.0 iec_sim['load_case'] = ( sim_types.kIecCaseExtremeWindShearHorizontal) elif case_name == '1.5b': iec_sim['event_t_start'] = 20.0 iec_sim['load_case'] = (sim_types.kIecCaseExtremeWindShearVertical) # Power production plus occurrence of fault. # # I'm not sure how the different types of IEC faults should be interpreted, # so I'm assuming rotor-out, servo-out, GSG fails faults. # # DLC 2.1: NTM V_in < V_hub < V_out, Control system fault, Ultimate elif case_name == '2.1': iec_sim['load_case'] = sim_types.kIecCaseNormalTurbulenceModel phys_sim['wind_speed'] = self._v_hub overrides['sim']['faults_sim'] = [self._fault_power_sys_zero] # DLC 2.2: NTM V_in < V_hub < V_out, Protection system fault, Ultimate elif case_name == '2.2': iec_sim['load_case'] = sim_types.kIecCaseNormalTurbulenceModel phys_sim['wind_speed'] = self._v_hub overrides['sim']['faults_sim'] = [{ 't_start': 20.0, 't_end': 25.0, 'component': 'GSG/elevation[0]', 'type': sim_types.kSimFaultMeasurementRescale, 'parameters': [0.0] }, { 't_start': 20.0, 't_end': 25.0, 'component': 'GSG/elevation[1]', 'type': sim_types.kSimFaultMeasurementRescale, 'parameters': [0.0] }] # DLC 2.3: EOG V_hub = V_r-2, V_r+2, V_out, Electrical fault, Ultimate elif case_name.startswith('2.3'): iec_sim['event_t_start'] = 20.0 iec_sim['load_case'] = sim_types.kIecCaseExtremeOperatingGust overrides['sim']['faults_sim'] = [self._fault_power_sys_zero] if case_name == '2.3b': phys_sim['wind_speed'] = self._v_hub_sensed - 2.0 elif case_name == '2.3c': phys_sim['wind_speed'] = self._v_hub_sensed + 2.0 # DLC 2.4: NTM V_in < V_hub < V_out, Fault, Fatigue elif case_name == '2.4': iec_sim['load_case'] = sim_types.kIecCaseNormalTurbulenceModel phys_sim['wind_speed'] = self._v_hub overrides['sim']['faults_sim'] = [{ 't_start': 150.0, 't_end': 170.0, 'component': 'Servo[1]', 'type': sim_types.kSimFaultActuatorZero, }] # Start up # DLC 3.1: NWP V_in < V_hub < V_out, Fatigue elif case_name == '3.1': iec_sim['load_case'] = sim_types.kIecCaseNormalWindProfile # DLC 3.2: EOG V_hub = V_in, V_r-2, V_r+2, V_out, Ultimate elif case_name.startswith('3.2'): iec_sim['event_t_start'] = 10.0 iec_sim['load_case'] = sim_types.kIecCaseExtremeOperatingGust phys_sim['wind_speed'] = self._v_in_sensed if case_name == '3.2b': phys_sim['wind_speed'] = self._v_hub_m2_sensed elif case_name == '3.2c': phys_sim['wind_speed'] = self._v_hub_p2_sensed elif case_name == '3.2d': phys_sim['wind_speed'] = self._v_out_sensed # DLC 3.3: EDC V_hub = V_in, V_r-2, V_r+2, V_out, Ultimate elif case_name.startswith('3.3'): iec_sim['event_t_start'] = 10.0 iec_sim['load_case'] = sim_types.kIecCaseExtremeDirectionChange phys_sim['wind_speed'] = self._v_in_sensed if case_name == '3.3b': phys_sim['wind_speed'] = self._v_hub_m2_sensed elif case_name == '3.3c': phys_sim['wind_speed'] = self._v_hub_p2_sensed elif case_name == '3.3d': phys_sim['wind_speed'] = self._v_out_sensed # Normal shut down # DLC 4.1: NWP V_in < V_hub < V_out, Fatigue elif case_name == '4.1': iec_sim['load_case'] = sim_types.kIecCaseNormalWindProfile # DLC 4.2: EOG V_hub = V_r-2, V_r+2, V_out, Ultimate elif case_name.startswith('4.2'): iec_sim['event_t_start'] = 50.0 iec_sim['load_case'] = sim_types.kIecCaseExtremeOperatingGust if case_name == '4.2a': phys_sim['wind_speed'] = self._v_hub_m2_sensed elif case_name == '4.2b': phys_sim['wind_speed'] = self._v_hub_p2_sensed elif case_name == '4.2c': phys_sim['wind_speed'] = self._v_out_sensed else: raise ValueError('Unknown IEC Case: ' + case_name) # Missing cases: # - Emergency shut down. # - Parked (standing still or idling). # - Parked and fault conditions. # - Transport assembly, maintenance, and repair. return mconfig.MakeParams( 'common.all_params', overrides_util.PreprocessOverrides(overrides), override_method='derived')
# See the License for the specific language governing permissions and # limitations under the License. """Report the various flap limits applied in the system.""" import collections import math import os import makani from makani.avionics.bootloader import system_config from makani.avionics.firmware.params import codec from makani.avionics.network import network_config from makani.config import mconfig from makani.control import system_types _CONFIG = mconfig.MakeParams('common.all_params') _SERVOS = ['A' + f for f in '124578'] + ['E1', 'E2', 'R1', 'R2'] _FLAPS = _SERVOS[0:6] + ['Elevator', 'Rudder'] def GetSimLimits(): servo_limits = [ _CONFIG['sim']['servos_sim'][i]['servo_drive'] for i in range(len(_SERVOS)) ] return { _SERVOS[i]: (servo_limits[i]['ref_model_min_position_limit'], servo_limits[i]['ref_model_max_position_limit']) for i in range(len(_SERVOS)) }
pyplot.ylabel('Thrust error [N]') pyplot.title('Thrust fit error') pyplot.xlim(rotor_model.omegas[[0, -1]]) print rotor_database_path print ' Max Thrust (v_freestream = 0.0): %g' % ( rotor_model.CalcMaxThrusts([0.0])[0][0]) print ' Max Thrust (v_freestream = 40.0): %g' % ( rotor_model.CalcMaxThrusts([40.0])[0][0]) def main(argv): # Parse flags. try: argv = FLAGS(argv) except gflags.FlagsError, e: print '\nError: %s\n\nUsage: %s ARGS\n%s' % (e, sys.argv[0], FLAGS) sys.exit(1) propellers = mconfig.MakeParams('m600.propellers') for i in range(system_types.kNumPropVersions): PlotSimpleRotorModel( FLAGS.air_density, propellers[i]['database']['name'], FLAGS.power_limit, FLAGS.torque_limit, FLAGS.tip_speed_limit, FLAGS.advance_ratio_stall_margin, FLAGS.num_curves) pyplot.show() if __name__ == '__main__': main(sys.argv)
pyplot.xlabel('Rotor Speed [rad/s]') pyplot.ylabel('Airspeed [m/s]') def main(argv): # Parse flags. try: argv = FLAGS(argv) except gflags.FlagsError, e: print '\nError: %s\n\nUsage: %s ARGS\n%s' % (e, sys.argv[0], FLAGS) sys.exit(1) if len(argv) > 1: propeller_paths = argv[1:] else: propellers = mconfig.MakeParams('prop.propellers') propeller_paths = [ os.path.join(makani.HOME, 'database', propeller['database']['name']) for propeller in propellers ] linked_axes = None for i, propeller_path in enumerate(propeller_paths): prop = load_database.LoadPropDatabase(propeller_path) fig = pyplot.figure(i + 1) fig.suptitle(os.path.basename(propeller_path)) fig.canvas.set_window_title(os.path.basename(propeller_path)) if linked_axes: linked_axes = pyplot.subplot(1, 3, 1, sharex=linked_axes, sharey=linked_axes)
def __init__(self, **kwargs): base_params = mconfig.MakeParams('common.all_params', overrides=None, override_method='simple') # Time [s] to end simulation. if FLAGS.flight_plan == 'TurnKey': end_time = 1500.0 elif FLAGS.flight_plan == 'HighHover': end_time = 1000.0 else: assert False, 'Unsupported flight plan: %s' % FLAGS.flight_plan flight_plan = 'kFlightPlan' + FLAGS.flight_plan # Build the list of tables. raw_base_overrides = { 'system': { 'flight_plan': flight_plan }, 'sim': { 'phys_sim': { 'wind_model': ('kWindModelDatabase' if FLAGS.turbsim_folder else FLAGS.wind_model) }, 'telemetry_sample_period': 0.0, 'sim_opt': [ 'kSimOptExitOnCrash', 'kSimOptFaults', 'kSimOptGroundContact', 'kSimOptImperfectSensors', 'kSimOptPerch', 'kSimOptPerchContact', 'kSimOptStackedPowerSystem' ], 'sim_time': end_time } } # Only use sensor imperfections in Monte Carlo simulations. if FLAGS.monte_carlo: raw_base_overrides['sim']['sim_opt'] += ['kSimOptImperfectSensors'] if FLAGS.offshore: raw_base_overrides['system']['test_site'] = 'kTestSiteNorway' else: raw_base_overrides['system']['test_site'] = 'kTestSiteParkerRanch' # Update raw_base_overrides with flag base_overrides. raw_base_overrides = dict_util.UpdateNestedDict( raw_base_overrides, json.loads(FLAGS.base_overrides)) if ('system' in raw_base_overrides and 'wing_serial' in raw_base_overrides['system']): assert False, ( 'Cannot override wing_serial; ' 'it is set implicitly by flight plan and wing_model.') y_ranges = [] if FLAGS.turbsim_folder: # Initialize object for selecting appropriate wind databases for each run. turbsim_database_selector = turbsim_util.TurbSimDatabaseSelector( FLAGS.turbsim_folder, base_params) # TODO: Rather than hardcode the shear reference height, # pull from a database/file with properties of each database set. # Also think about overriding this value in the base_params. x_range = parameter_tables.WindSpeedParameterRange( FLAGS.wind_speeds, wind_shear_ref_height_agl=21.0) if FLAGS.monte_carlo: # TODO: Pull the range of options for these from a # database/file with properties of each database set. Would need to be # able to query a sheet w/ these parameters; maybe make a descriptive # csv or json file in each database folder. y_ranges += [ parameter_tables.WindDatabaseInitialTimeParameterRange( [30.0, 270.0], distribution={ 'lower_bound': 30.0, 'upper_bound': 270.0, 'type': 'uniform' }), parameter_tables.WindDatabaseYOffsetParameterRange( [-100.0, 100.0], distribution={ 'lower_bound': -100.0, 'upper_bound': 100.0, 'type': 'uniform' }), ] else: turbsim_database_selector = None # Sweep wind speeds. # If times for wind speed updates are specified (a 3-by-1 list of time # values in seconds), then saturate the mean wind speed to # FLAGS.max_hover_mean_wind_speed before the second time entry and after # the third time entry. # NOTE: # - This flag will not affect simulations which use a TurbSim database. # - Here, the second value in t_updates corresponds to the approximate # time when the kite enters crosswind, and the third value is the time # when the joystick throttle is updated to # kSimJoystickThrottleReturnToPerch. x_range = parameter_tables.WindSpeedParameterRange( FLAGS.wind_speeds, wind_shear_ref_height_agl=base_params['sim']['phys_sim'] ['wind_shear_ref_height_agl'], t_updates=[0.0, 460.0, 820.0], max_wind_speed=FLAGS.max_hover_mean_wind_speed) # Sweep environmental properties. # Original source for WindElevation is unknown. # TODO: Update WindElevation distribution based on Parker Ranch # wind measurements. # Wind veer range is based on Parker Ranch wind measurements as discussed # in http://b/117942530. y_ranges += [ parameter_tables.WindElevationDegParameterRange([-6.0, 6.0], distribution={ 'mean': 0.0, 'sigma': 3.0, 'bound': 2.0, 'type': 'normal' }), parameter_tables.WindVeerDegParameterRange( [-45.0, -21.0, 21.0, 45.0], distribution={ 'mean': 13.5, 'sigma': 7.5, 'bound': 3.0, 'type': 'normal' }), ] shear_parameter_range = parameter_tables.WindShearExponentParameterRange( FLAGS.wind_shears) # Add shear exponents as a sweep if this is not a Monte Carlo run. if not FLAGS.monte_carlo: y_ranges.append(shear_parameter_range) # Sweep mass properties. y_ranges += [ # Center of mass location: maximum 0.05 m error in each dimension for # Monte Carlo sweeps, and 0.05 m variation in crosswind parameters # sweeps. parameter_tables.CenterOfMassOffsetParameterRange( 0, [-0.05, 0.05], distribution={ 'mean': 0.0, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }, body='wing_sim', body_name='Wing'), parameter_tables.CenterOfMassOffsetParameterRange( 1, [-0.05, 0.05], distribution={ 'mean': 0.0, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }, body='wing_sim', body_name='Wing'), parameter_tables.CenterOfMassOffsetParameterRange( 2, [-0.05, 0.05], distribution={ 'mean': 0.0, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }, body='wing_sim', body_name='Wing'), # Mass scaling: 1.4% error for both Monte Carlo and crosswind sweeps. parameter_tables.MassScaleParameterRange([0.986, 1.014], distribution={ 'mean': 1.0, 'sigma': 0.014, 'bound': 2.0, 'type': 'normal' }, body='wing_sim', body_name='Wing'), # Inertia scaling: maximum errors for Monte Carlo sweeps of 5% for Ixx, # 2% for Iyy and 4.4% for Izz. 10% variation in crosswind sweeps. parameter_tables.InertiaScaleParameterRange(0, [0.90, 1.1], distribution={ 'mean': 1.0, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }, body='wing_sim', body_name='Wing'), parameter_tables.InertiaScaleParameterRange(1, [0.90, 1.1], distribution={ 'mean': 1.0, 'sigma': 0.01, 'bound': 2.0, 'type': 'normal' }, body='wing_sim', body_name='Wing'), parameter_tables.InertiaScaleParameterRange(2, [0.90, 1.1], distribution={ 'mean': 1.0, 'sigma': 0.022, 'bound': 2.0, 'type': 'normal' }, body='wing_sim', body_name='Wing'), ] # Sweep aerodynamics parameters. # The following parameters override existing aerodynamic offsets in our # config files. if FLAGS.wing_model == 'm600': y_ranges += [ # CD offset: maximum 0.050 error. # Known offset in CD from RPX-07 glide data analysis is 0.075. # Use 0.075 as mean for Monte Carlo sweeps. # This is chosen since this will override existing offsets. # [0.075 - 0.050, 0.075 + 0.050] variation in crosswind sweeps. parameter_tables.AeroSimOffsetParameterRange( 'CD', [0.025, 0.125], distribution={ 'mean': 0.075, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }), # CL offset: maximum 0.3 error for Monte Carlo sweeps, # 0.2 variation in crosswind sweeps. # Flight data and aero database comparison shows CL is over predicted, # thus setting the mean at -0.125 from Monte Carlo sweeps. parameter_tables.AeroSimOffsetParameterRange( 'CL', [-0.2, 0.2], distribution={ 'mean': -0.125, 'sigma': 0.15, 'bound': 2.0, 'type': 'normal' }), ] + parameter_sweeps.GetAeroDerivativeParameterRanges() elif FLAGS.wing_model == 'oktoberkite': y_ranges += [ parameter_tables.AeroSimOffsetParameterRange( 'CD', [-0.011, 0.033], distribution={ 'mean': 0.011, 'sigma': 0.011, 'bound': 2.0, 'type': 'normal' }), parameter_tables.AeroSimOffsetParameterRange( 'CL', [-0.275, 0.275], distribution={ 'mean': 0.0, 'sigma': 0.15, 'bound': 2.0, 'type': 'normal' }), ] + parameter_sweeps.GetAeroDerivativeParameterRanges() else: assert False, '{} wing_model is not supported.'.format( FLAGS.wing_model) # Sweep Pitot parameters. y_ranges += [ # Pitot angles offsets: maximum 1 deg offset for Monte Carlo and # crosswind sweeps. # Pitot Cp offset: maximum 0.01 offset for Monte Carlo and # 0.02 variation in crosswind sweeps. parameter_tables.PitotPitchDegParameterRange([-1.0, 1.0], distribution={ 'mean': 0.0, 'sigma': 0.5, 'bound': 2.0, 'type': 'normal' }), parameter_tables.PitotYawDegParameterRange([-1.0, 1.0], distribution={ 'mean': 0.0, 'sigma': 0.5, 'bound': 2.0, 'type': 'normal' }), parameter_tables.PitotCpOffsetParameterRange([-0.02, 0.02], distribution={ 'mean': 0.0, 'sigma': 0.01, 'bound': 2.0, 'type': 'normal' }), ] # Sweep actuator parameters. y_ranges += parameter_sweeps.GetFlapOffsetParameterRanges() # Sweep offshore parameters. if FLAGS.offshore: # Current uncertainties: # - 5% on mass properties. # - 5% on hydrodynamic model parameters. # - 5% on mooring line model parameters. # - 5 deg on yaw equilibrium heading. # - 50 cm on axial mooring line attachment point model. # - 20 cm on orthogonal mooring line attachment point model. # TODO: Refine the parameter variations once we have measured # data and knowledge of sensitivity. # TODO: Add a deterministic offshore crosswind sweep to the # nightly (b/137648033). y_ranges += [ # Center of mass offset. parameter_tables.CenterOfMassOffsetParameterRange( 0, [-0.2, 0.2], distribution={ 'mean': 0.0, 'sigma': 0.1, 'bound': 2.0, 'type': 'normal' }, body='buoy_sim', body_name='Buoy'), parameter_tables.CenterOfMassOffsetParameterRange( 1, [-0.2, 0.2], distribution={ 'mean': 0.0, 'sigma': 0.1, 'bound': 2.0, 'type': 'normal' }, body='buoy_sim', body_name='Buoy'), parameter_tables.CenterOfMassOffsetParameterRange( 2, [-1.0, 1.0], distribution={ 'mean': 0.0, 'sigma': 0.39, 'bound': 2.0, 'type': 'normal' }, body='buoy_sim', body_name='Buoy'), # Mass scaling. parameter_tables.MassScaleParameterRange([0.9, 1.1], distribution={ 'mean': 1.0, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }, body='buoy_sim', body_name='Buoy'), # Inertia scaling. parameter_tables.InertiaScaleParameterRange(0, [0.9, 1.1], distribution={ 'mean': 1.0, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }, body='buoy_sim', body_name='Buoy'), parameter_tables.InertiaScaleParameterRange(1, [0.9, 1.1], distribution={ 'mean': 1.0, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }, body='buoy_sim', body_name='Buoy'), parameter_tables.InertiaScaleParameterRange(2, [0.9, 1.1], distribution={ 'mean': 1.0, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }, body='buoy_sim', body_name='Buoy'), # Hydrodynamic model uncertainties. parameter_tables.BuoyModelParameterRange( 'Buoy Torsional Damping X Scale [#]', [0.9, 1.1], distribution={ 'mean': 1.0, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }, category='hydrodynamics', variable='torsional_damping_x_scale'), parameter_tables.BuoyModelParameterRange( 'Buoy Torsional Damping Y Scale [#]', [0.9, 1.1], distribution={ 'mean': 1.0, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }, category='hydrodynamics', variable='torsional_damping_y_scale'), parameter_tables.BuoyModelParameterRange( 'Buoy Torsional Damping Z Scale [#]', [0.9, 1.1], distribution={ 'mean': 1.0, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }, category='hydrodynamics', variable='torsional_damping_z_scale'), parameter_tables.BuoyModelParameterRange( 'Buoy Buoyancy Damping Coeff. Scale [#]', [0.9, 1.1], distribution={ 'mean': 1.0, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }, category='hydrodynamics', variable='buoyancy_damping_coeff_scale'), parameter_tables.BuoyModelParameterRange( 'Buoy Added Mass Coeff. Scale [#]', [0.9, 1.1], distribution={ 'mean': 1.0, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }, category='hydrodynamics', variable='Ca_scale'), parameter_tables.BuoyModelParameterRange( 'Buoy Effective Heave Diameter Scale [#]', [0.9, 1.1], distribution={ 'mean': 1.0, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }, category='hydrodynamics', variable='Dh_scale'), parameter_tables.BuoyModelParameterRange( 'Buoy Added Inertia Coeff. Scale [#]', [0.9, 1.1], distribution={ 'mean': 1.0, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }, category='hydrodynamics', variable='ki_scale'), # Mooring line model uncertainties. parameter_tables.BuoyModelParameterRange( 'Buoy Equilibrium Yaw Angle Delta [deg]', np.arange(-180., 180., 30.), distribution={ 'mean': 0.0, 'sigma': 2.5, 'bound': 2.0, 'type': 'normal' }, category='mooring_lines', variable='yaw_equilibrium_heading_delta'), parameter_tables.BuoyModelParameterRange( 'Buoy Yaw Torsional Stiffness Scale [#]', [0.9, 1.1], distribution={ 'mean': 1.0, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }, category='mooring_lines', variable='torsional_stiffness_z_scale'), parameter_tables.BuoyModelParameterRange( 'Buoy Effective Mooring Line X Attachment Delta [m]', [-0.2, 0.2], distribution={ 'mean': 0.0, 'sigma': 0.1, 'bound': 2.0, 'type': 'normal' }, category='mooring_lines', variable='mooring_attach_pos_x_delta'), parameter_tables.BuoyModelParameterRange( 'Buoy Effective Mooring Line Y Attachment Delta [m]', [-0.2, 0.2], distribution={ 'mean': 0.0, 'sigma': 0.1, 'bound': 2.0, 'type': 'normal' }, category='mooring_lines', variable='mooring_attach_pos_y_delta'), parameter_tables.BuoyModelParameterRange( 'Buoy Effective Mooring Line Z Attachment Delta [m]', [-0.5, 0.5], distribution={ 'mean': 0.0, 'sigma': 0.25, 'bound': 2.0, 'type': 'normal' }, category='mooring_lines', variable='mooring_attach_pos_z_delta'), parameter_tables.BuoyModelParameterRange( 'Buoy Mooring Line Linear Spring Coeff. Scale [#]', [0.9, 1.1], distribution={ 'mean': 1.0, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }, category='mooring_lines', variable='kt0_scale'), parameter_tables.BuoyModelParameterRange( 'Buoy Mooring Line Quadratic Spring Coeff. Scale [#]', [0.9, 1.1], distribution={ 'mean': 1.0, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }, category='mooring_lines', variable='kt1_scale'), parameter_tables.BuoyModelParameterRange( 'Buoy Mooring Line Damping Coeff. Scale [#]', [0.9, 1.1], distribution={ 'mean': 1.0, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }, category='mooring_lines', variable='ct_scale'), ] # Sweep environmental properties. # Air density at Stavanger test site is 1.220 kg/m^3. # Use this as mean value and maximum 5% error # in density measurement for Monte Carlo sweeps. For crosswind sweeps, # high limit is at sea-level density and low limit is at roughly 2500 m # density altitude. # NOTE: Ideally, we would like to point the mean value of # the air density parameter range to the air_density in the config # structure. However, it is a parameter that is derived from the test_site # param, which is also overridden at the current level. y_ranges += [ parameter_tables.AirDensityParameterRange([0.976, 1.225], distribution={ 'mean': 1.220, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }) ] # Parameters used to define the 'WaveParameterSelector' class used in # Monte Carlo analyses and define the distribution in the # parameter_tables. See the WaveParameterSelector and ParameterRange class # definitions in parameter_tables.py for more description. # Note, for example, that the 'mean' used in a lognormal distribution is # the mean of the underlying normal distribution, and can therefore be # outside the upper/lower_bounds. # Generated from Colab notebook: b/130682761#comment15 # Source: 'NORA10_5922N_0509E.txt' # Data filtered for Jun to Sep, between hours of 6 and 20, and when # wind speed at 10 m is between 5 and 15 m/s.' # Overwriting the upper_bound of the significant wave height to 3.0 m # to account for wave envelope due to max significant wave height for # personnel transfers at sea: b/130682761#comment18 wave_variation_params = { 'peak_period': { 'correlation_fit': { 'coefficient': 1.186394, 'description': 'TP = coefficient * HS + intercept' ' + (lognorm(mean, sigma) + loc)', 'intercept': 5.822409, 'lower_bound': 3.923101, 'upper_bound': 13.687049, }, 'description': 'Peak wave period of the total sea state, based on total sea ' 'significant wave height.', 'distribution': { 'loc': -4.342332, 'lower_bound': -2.478684, 'mean': 1.384812, 'sigma': 0.405295, 'type': 'lognormal', 'upper_bound': 4.217562, }, 'label': 'Peak wave period variation [s]', 'values': [-2.478684, 4.217562], }, 'significant_height': { 'correlation_fit': { 'coefficient': 0.012127, 'description': 'HS = coefficient * speed^2 + intercept + (lognorm(mean, ' 'sigma) + loc). Note the distribution is closer to ' 'exponnorm, but lognorm should be close enough and keeps ' 'things simpler.', 'intercept': 0.653568, 'lower_bound': 0.427477, 'upper_bound': 3.0, }, 'description': 'Significant wave height of the total sea state, ' 'based on wind speed at 10 m.', 'distribution': { 'loc': -2.647313, 'lower_bound': -0.854369, 'mean': 0.953525, 'sigma': 0.196548, 'type': 'lognormal', 'upper_bound': 1.108073, }, 'label': 'Significant wave height variation [m]', 'values': [-0.854369, 1.108073], }, 'wave_wind_alignment': { 'description': 'Alignment [deg] of total sea peak wave direction minus wind ' 'direction at 10 m. Corrected for use of wave heading ' 'as wave-direction-of-travel.', 'distribution': { 'bound': 2.000000, 'mean': 191.708319, 'sigma': 51.891563, 'type': 'normal', }, 'label': 'Wave alignment [deg] relative to wind', 'values': [87.925192, 295.491445], }, 'wind_direction': { 'description': 'Measured at 10 m.', 'distribution': { 'distributions': [{ 'mean': 170.364074, 'sigma': 29.122998, 'type': 'vonmises', 'units': 'deg', }, { 'mean': 335.172882, 'sigma': 14.044122, 'type': 'vonmises', 'units': 'deg', }], 'type': 'multimodal', 'weights': [0.510851, 0.489149], }, 'label': 'Wind direction [deg]', 'values': [170.364074, 335.172882], }, } y_ranges += [ parameter_tables.WindDirectionDegParameterRange( wave_variation_params['wind_direction']['values'], distribution=wave_variation_params['wind_direction'] ['distribution']) ] if FLAGS.randomize_waves: del wave_variation_params['wind_direction'] for param in wave_variation_params: y_ranges += [ parameter_tables.CustomParameterRange( wave_variation_params[param]['label'], ['sim', 'sea_sim', 'waves', param], wave_variation_params[param]['values'], wave_variation_params[param]['distribution']) ] wave_parameter_selector = parameter_tables.WaveParameterSelector( wave_variation_params) else: wave_parameter_selector = None else: wave_parameter_selector = None # Sweep environmental properties. # Air density at Parker Ranch test site is 1.026 kg/m^3. This is roughly # 1800 m density altitude. Use this as mean value and maximum 5% error # in density measurement for Monte Carlo sweeps. For crosswind sweeps, # high limit is at sea-level density and low limit is at roughly 2500 m # density altitude. y_ranges += [ parameter_tables.AirDensityParameterRange([0.976, 1.225], distribution={ 'mean': 1.026, 'sigma': 0.025, 'bound': 2.0, 'type': 'normal' }) ] # Wind at Parker Ranch test site is nominally from the NorthEast. # From the initial analysis of our 2018 Aug-Oct Lidar data at the test # site, the mean is roughly at 55 degrees azimuth. # From the analysis presented in go/makani-cw09-wind-envelope, the # operational envelope for the wind direction was set to [0 - 75] degrees. # The normal distribution is clipped 5 degrees past these limits. # NOTE: Wind direction is an epistemic random variable that # we treat as an aleatory random variable to simplify the Monte Carlo # analysis. y_ranges += [ parameter_tables.WindDirectionDegParameterRange( [30, 80], distribution={ 'mean': 55.0, 'sigma': 15.0, 'lower_bound': -5.0, 'upper_bound': 80.0, 'type': 'normal' }) ] base_overrides = overrides_util.PreprocessOverrides(raw_base_overrides) # Wipe out existing y_range if only_custom_sweep flag is passed. if FLAGS.only_custom_sweep: y_ranges = [] # Parse the custom_sweep flag. custom_sweep_entries = json.loads(FLAGS.custom_sweep) # Add custom sweep entries to y_ranges. for entry in custom_sweep_entries: y_ranges += [ parameter_tables.CustomParameterRange( entry['name'], entry['path'], # Values can be unspecified for monte-carlo usage. entry.get('values', None), # Distribution can be unspecifed for deterministic sweep usage. entry.get('distribution', None)) ] tables = [] if FLAGS.monte_carlo: num_cols = len(x_range.values) for shear_exponent in shear_parameter_range.values: for value in x_range.values: table_base_overrides = dict_util.MergeNestedDicts( base_overrides, x_range.GetOverrides(value)) table_base_overrides = dict_util.MergeNestedDicts( table_base_overrides, shear_parameter_range.GetOverrides(shear_exponent)) tables.append( parameter_tables.ParameterRangeMonteCarloTable( 'Monte Carlo %s = %g, shear exponent = %g' % (x_range.label, x_range.GetDisplayValue(value), shear_exponent), [FLAGS.monte_carlo_cols, FLAGS.monte_carlo_rows], y_ranges, base_overrides=table_base_overrides, turbsim_database_selector=turbsim_database_selector, wave_parameter_selector=wave_parameter_selector)) title = FLAGS.flight_plan + ' Monte Carlo' else: num_cols = 4 for y_range in y_ranges: tables.append( parameter_tables.ParameterRangeTable( y_range.label, x_range, y_range, base_overrides=base_overrides, turbsim_database_selector=turbsim_database_selector)) title = FLAGS.flight_plan + ' Parameter Sweeps' wing_model = wing_flag.FlagToWingModelName(FLAGS.wing_model) params = batch_sim_params.CrosswindSweepsParameters( flight_plan=flight_plan, wing_model=wing_model, offshore=FLAGS.offshore) super(CrosswindSweepsSimClient, self).__init__(FLAGS.output_dir, tables, params.scoring_functions, title=title, columns=num_cols, **kwargs)
wing_serials = [FLAGS.wing_serial_enum] if not _IsValidWingSerial(FLAGS.wing_serial_enum, FLAGS.wing_model): assert False, ( 'Wing model "%s" does not have serial number %d (%s).' % (FLAGS.wing_model, FLAGS.wing_serial_enum, _WING_SERIAL_HELPER.ShortName(FLAGS.wing_serial_enum))) controllers = [] for wing_serial in wing_serials: if not _IsValidWingSerial(wing_serial, FLAGS.wing_model): continue mconfig.WING_MODEL = FLAGS.wing_model wing_serial_is_active = mconfig.MakeParams( 'common.wing_serial_status', overrides={'wing_serial': wing_serial}, override_method='derived') if not wing_serial_is_active: continue print '\nWing serial: %s\n' % (_WING_SERIAL_HELPER.Name(wing_serial)) overrides = {'system': {'wing_serial': wing_serial}} all_params = mconfig.MakeParams('common.all_params', overrides=overrides, override_method='derived') CheckAeroSimParams(all_params) trans_in = ControlDesign(all_params['system'], all_params['control'], all_params['sim']) # TODO: Consider the impact of nonzero wind speed, with updates