def testWindModel(self): overrides_in = { 'sim': { 'phys_sim': { 'wind_model': sim_types.kWindModelDrydenTurbulence, }, }, } overrides_out = overrides_util.PreprocessOverrides(overrides_in) self.assertEqual(sim_types.kWindModelDrydenTurbulence, overrides_out['sim']['phys_sim']['wind_model']) overrides_in = { 'sim': { 'phys_sim': { 'wind_model': 'DrydenTurbulence', }, }, } overrides_out = overrides_util.PreprocessOverrides(overrides_in) self.assertEqual(sim_types.kWindModelDrydenTurbulence, overrides_out['sim']['phys_sim']['wind_model']) overrides_in = { 'sim': { 'phys_sim': { 'wind_model': 'NotAWindModel', }, }, } with self.assertRaises(c_helpers.EnumError): overrides_util.PreprocessOverrides(overrides_in)
def testFaultsSimError(self): overrides_in = { 'sim': { 'faults_sim': [{ 't_start': 100.0, 'component': 'Pitot/actual_P_dyn', 'type': 'kSimFaultMeasurementRescale', }] * (1 + sim_types.MAX_FAULT_EVENTS) } } with self.assertRaises(overrides_util.InvalidArrayLength): overrides_util.PreprocessOverrides(overrides_in) overrides_in = { 'sim': { 'faults_sim': [{ 't_start': 100.0, 'component': 'Pitot/actual_P_dyn', 'type': 'kSimFaultMeasurementRescale', 'parameters': [0.0] * (1 + sim_types.MAX_FAULT_EVENT_PARAMETERS) }] } } with self.assertRaises(overrides_util.InvalidArrayLength): overrides_util.PreprocessOverrides(overrides_in)
def testWindSpeedUpdates(self): updates_in = [{ 't_update': 20.0, 'offset': -5.0 }, { 't_update': 40.0, 'offset': 10.0 }] overrides_struct = { 'sim': { 'phys_sim': { 'wind_speed_update': updates_in } } } overrides = overrides_util.PreprocessOverrides(overrides_struct) self.assertTrue( 'num_updates' in overrides['sim']['phys_sim']['wind_speed_update'] and 'offsets' in overrides['sim']['phys_sim']['wind_speed_update']) self.assertEqual( 2, overrides['sim']['phys_sim']['wind_speed_update']['num_updates']) self.assertEqual( sim_types.MAX_WIND_SPEED_UPDATES, len(overrides['sim']['phys_sim']['wind_speed_update']['offsets']))
def testSimOpt(self): overrides_struct = {'sim': {'sim_opt': ['kSimOptImperfectSensors', 1]}} overrides = overrides_util.PreprocessOverrides(overrides_struct) self.assertIn('sim', overrides) self.assertIn('sim_opt', overrides['sim']) self.assertEqual(1 | sim_types.kSimOptImperfectSensors, overrides['sim']['sim_opt'])
def testJoystickSimError(self): overrides_in = { 'sim': { 'joystick_sim': { 'updates': [{ 't_update': 100.0, 'type': 'kSimJoystickUpdateSwitchMiddle' }] * (1 + sim_types.MAX_JOYSTICK_UPDATES) } } } with self.assertRaises(overrides_util.InvalidArrayLength): overrides_util.PreprocessOverrides(overrides_in)
def testJoystickSimJoystickType(self): overrides_struct = { 'sim': { 'joystick_sim': { 'joystick_type': 'Hardware' } } } overrides = overrides_util.PreprocessOverrides(overrides_struct) self.assertTrue( 'sim' in overrides and 'joystick_sim' in overrides['sim'] and 'joystick_type' in overrides['sim']['joystick_sim']) self.assertEqual(sim_types.kSimJoystickTypeHardware, overrides['sim']['joystick_sim']['joystick_type'])
def testFaultsSim(self): faults_in = [{ 't_start': 100.0, 't_end': 200.0, 'component': 'Pitot/actual_P_dyn', 'type': 'kSimFaultMeasurementRescale', 'parameters': [1.0, 2.0, 3.0], }, { 't_start': 100.0, 'component': 'Pitot/actual_P_dyn', 'type': 'kSimFaultMeasurementRescale', }] overrides_struct = {'sim': {'faults_sim': faults_in}} overrides = overrides_util.PreprocessOverrides(overrides_struct) # Check the overall structure of the created fields. self.assertTrue( 'sim' in overrides and 'faults_sim' in overrides['sim'] and 'num_fault_events' in overrides['sim']['faults_sim'] and 'fault_events' in overrides['sim']['faults_sim']) self.assertEqual(2, overrides['sim']['faults_sim']['num_fault_events']) faults_out = overrides['sim']['faults_sim']['fault_events'] self.assertEqual(sim_types.MAX_FAULT_EVENTS, len(faults_out)) # Check the fault events that we specified. for (fault_in, fault_out) in zip(faults_in, faults_out[:len(faults_in)]): self.assertEqual(fault_in['component'], fault_out['component']) self.assertEqual(sim_types.kSimFaultMeasurementRescale, fault_out['type']) if 'parameters' in fault_in: params_in = fault_in['parameters'] params_out = fault_out['parameters'] self.assertEqual(sim_types.MAX_FAULT_EVENT_PARAMETERS, len(params_out)) self.assertEqual(params_in, params_out[:len(params_in)]) else: self.assertEqual([0.0] * sim_types.MAX_FAULT_EVENT_PARAMETERS, fault_out['parameters']) # Check basic reasonableness of the unused faults. for fault in faults_out[len(faults_in):]: self.assertEqual(sim_types.kSimFaultNoFault, fault['type']) self.assertEqual(sim_types.MAX_FAULT_EVENT_PARAMETERS, len(fault['parameters']))
def testJoystickSimUpdates(self): updates_in = [{ 't_update': 100.0, 'type': 'kSimJoystickUpdateSwitchMiddle' }, { 't_update': 100.0, 'type': 'kSimJoystickUpdateThrottle', 'enum_value': 'kSimJoystickThrottleManual', 'value': 22.0 }, { 't_update': 200.0, 'type': 'kSimJoystickUpdateThrottle', 'enum_value': 'kSimJoystickThrottleCrosswindNormal' }, { 't_update': 200.0, 'type': 'kSimJoystickUpdateThrottle', 'enum_value': 'kSimJoystickThrottleCrosswindNormal', 'value': -1.0 }, { 't_update': 200.0, 'type': 'kSimJoystickUpdateSwitchMiddle', 'enum_value': 0 }] overrides_struct = {'sim': {'joystick_sim': {'updates': updates_in}}} overrides = overrides_util.PreprocessOverrides(overrides_struct) self.assertTrue('sim' in overrides and 'joystick_sim' in overrides['sim'] and 'num_updates' in overrides['sim']['joystick_sim'] and 'updates' in overrides['sim']['joystick_sim']) self.assertEqual(5, overrides['sim']['joystick_sim']['num_updates']) self.assertEqual(sim_types.MAX_JOYSTICK_UPDATES, len(overrides['sim']['joystick_sim']['updates'])) self.assertEqual( 0.0, overrides['sim']['joystick_sim']['updates'][0]['value']) for update in overrides['sim']['joystick_sim']['updates'][ len(updates_in):]: self.assertEqual(sim_types.kSimJoystickUpdateNone, update['type'])
assert False, '--input_file must be specified for --type=monitor.' elif FLAGS.type == 'sim': assert False, '--input_file must be specified for --type=sim.' elif FLAGS.type == 'system': assert False, '--input_file must be specified for --type=system.' else: assert False, '--type was not properly validated.' # 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':
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)
def __init__(self, **kwargs): # The scale of the disturbances were calculated based on the following. # # m = 1499 # Mass of the wing [kg]. # g = 9.81 # Gravitational acceleration [m/s^2] # num_rotors = 8 # Number [#] of rotors. # V = 15 # Wind speed [m/s]. # y_motor = 3.6 # Distance [m] to the COM from the outboard motors. # z_motor = 1.6 # Distance [m] to the COM from the bottom row motors. # Vgust = 4 # Increased wind speed [m/s] due to a gust. # Cd = 1.2 # Flat plate drag coefficient [#]. # rho = 1.225 # Air density [kg / m^3]. # c = 1.28 # Wing chord [m]. # b = 26 # Wing span [m]. # A_ele = 4 # Elevator area [m^2]. # A_rud = 4 # Rudder area [m^2]. # r_tail = 7 # Distance of tail from COM [m]. # # # For roll we consider a gust on the tip of the wing: # # roll_disturbance = 0.5 * rho * Cd * c * ( # (V+Vgust)**2.0 * 0.5 * ((b/2.0)**2.0 - (0.75 * b/2.0)**2.0) # - V**2.0 * 0.5 * ((b/2.0)**2.0 - (0.75 * b/2.0)**2.0)) # # For pitch we consider the elevator suddenly shifting to be 90 # deg to the wind or a bottom row motor suddenly being unable to # provide thrust. # # pitch_disturbance = [0.5 * rho * Cd * A_ele * V**2 * r_tail, # z_motor * m * g / num_rotors] # # For yaw we consider a sudden 45 deg. shift in wind conditions # hitting the rudder or an outboard propellor suddenly being # unable to provide thrust. # # yaw_disturbance = [(0.5 * rho * Cd * A_rud * # (numpy.cos(numpy.pi / 4.0) * V)**2.0 * r_tail), # y_motor * m * g / num_rotors] # # We then increase these numbers by 50 percent. # Different disturbance directions. # NOTE: The way that FaultParameterRange uses numpy.min and # numpy.max downstream of disturbances_data restricts these # arrays to have only a single component in a 0, 1, or 2 # direction. This makes the min/max for a parameter sweep on # a batch sim result page for a single sweep look a little odd # as well, but it remains functional. Eventually someone may # want multi-axis disturbances and this will have to be fixed. disturbances_data = [ ('Pos. Roll', ([1500.0, 0.0, 0.0], [7500.0, 0.0, 0.0])), ('Pos. Pitch', ([0.0, 1500.0, 0.0], [0.0, 7500.0, 0.0])), ('Pos. Yaw', ([0.0, 0.0, 1500.0], [0.0, 0.0, 10500.0])), ('Neg. Roll', ([-7500, 0.0, 0.0], [-1500.0, 0.0, 0.0])), ('Neg. Pitch', ([0.0, -7500.0, 0.0], [0.0, -1500.0, 0.0])), ('Neg. Yaw', ([0.0, 0.0, -10500.0], [0.0, 0.0, -7500.0])) ] params = batch_sim_params.HoverDisturbancesParameters() t_start = params.setup_time t_end = params.setup_time + params.impulse_duration disturbances = [] for (disturbance_name, parameters) in disturbances_data: name = '%g second %s Torque Step' % (t_end - t_start, disturbance_name) disturbances += [(name, TorqueStepAmplitudeRange(name, t_start, t_end, parameters[0], parameters[1]))] wind_speeds = parameter_tables.WindSpeedParameterRange( FLAGS.wind_speeds) base_overrides = overrides_util.PreprocessOverrides({ 'system': { 'flight_plan': 'kFlightPlanStartDownwind' }, 'control': { 'hover': { 'inject': { 'use_signal_injection': False } } }, 'sim': { 'sim_opt': [ 'Faults', 'GroundContact', 'ImperfectSensors', 'Perch', 'PerchContact', 'StackedPowerSystem' ], 'telemetry_sample_period': 0.0, 'sim_time': (params.setup_time + params.impulse_duration + params.settle_time), } }) super(HoverDisturbancesSimClient, self).__init__(FLAGS.output_dir, base_overrides, wind_speeds, disturbances, params.scoring_functions, title='Hover Disturbances', **kwargs)
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')
def testWingSimXg0(self): # Test Cartesian coordinates. with self.assertRaises(overrides_util.InvalidArgument): overrides_util.PreprocessOverrides( {'sim': { 'wing_sim': { 'Xg_0': [22.0, 22.0] } }}) with self.assertRaises(overrides_util.InvalidArgument): overrides_util.PreprocessOverrides( {'sim': { 'wing_sim': { 'Xg_0': [22.0, 22.0, 22.0, 22.0] } }}) overrides = overrides_util.PreprocessOverrides( {'sim': { 'wing_sim': { 'Xg_0': [22.0, 22.0, 22.0] } }}) self.assertTrue('sim' in overrides and 'wing_sim' in overrides['sim'] and 'Xg_0' in overrides['sim']['wing_sim']) self.assertEqual([22.0, 22.0, 22.0], overrides['sim']['wing_sim']['Xg_0']) # Test spherical coordinates. with self.assertRaises(overrides_util.InvalidArgument): overrides_util.PreprocessOverrides({ 'sim': { 'wing_sim': { 'Xg_0': { 'ele': 22.0, 'azi': 0.0, 'r': 0.0, 'bad': 0.0 } } } }) with self.assertRaises(overrides_util.InvalidArgument): overrides_util.PreprocessOverrides( {'sim': { 'wing_sim': { 'Xg_0': { 'ele': 22.0, 'azi': 0.0 } } }}) overrides = overrides_util.PreprocessOverrides({ 'sim': { 'wing_sim': { 'Xg_0': { 'ele': numpy.pi / 3, 'azi': 0.0, 'r': 1.0 } } } }) self.assertTrue('sim' in overrides and 'wing_sim' in overrides['sim'] and 'Xg_0' in overrides['sim']['wing_sim']) expected = [-0.5, 0.0, -numpy.sqrt(3.0) / 2.0] for i in range(3): self.assertAlmostEqual(expected[i], overrides['sim']['wing_sim']['Xg_0'][i])
def testBadEnumValues(self): # Test overriding the flight_plan. with self.assertRaises(c_helpers.EnumError): overrides_util.PreprocessOverrides( {'system': { 'flight_plan': 'kFlightPlanEit' }}) # Test a name that doesn't start with FLIGHT_PLAN. with self.assertRaises(c_helpers.EnumError): overrides_util.PreprocessOverrides( {'system': { 'flight_plan': 'kSimOptPerch' }}) # Test a name that doesn't exist. with self.assertRaises(c_helpers.EnumError): overrides_util.PreprocessOverrides( {'sim': { 'sim_opt': ['kSimOptEit'] }}) # Test a name that doesn't start with SIM_OPT. with self.assertRaises(c_helpers.EnumError): overrides_util.PreprocessOverrides( {'sim': { 'sim_opt': ['kFlightPlanTurnKey'] }}) # Test a name that doesn't exist. with self.assertRaises(c_helpers.EnumError): overrides_util.PreprocessOverrides( {'sim': { 'faults_sim': [{ 'type': 'kSimFaultUpdateEit' }] }}) # Test a name that doesn't start with SIM_FAULT. with self.assertRaises(c_helpers.EnumError): overrides_util.PreprocessOverrides( {'sim': { 'faults_sim': [{ 'type': 'kSimOptPerch' }] }}) # Test a name that doesn't exist. with self.assertRaises(c_helpers.EnumError): overrides_util.PreprocessOverrides({ 'sim': { 'joystick_sim': { 'updates': [{ 'type': 'kSimJoystickUpdateEit' }] } } }) # Test a name that doesn't start with SIM_JOYSTICK_UPDATE. with self.assertRaises(c_helpers.EnumError): overrides_util.PreprocessOverrides({ 'sim': { 'joystick_sim': { 'updates': [{ 'type': 'kSimJoystickTypeProgrammed' }] } } })