From 69ed95cebd4aab61a0a0cd7b1007ad8838a1ad2c Mon Sep 17 00:00:00 2001 From: misi9170 <39596329+misi9170@users.noreply.github.com> Date: Fri, 20 Sep 2024 13:34:49 -0400 Subject: [PATCH] Enable heterogeneous inflow in `FlorisStandin` (#112) * Example code. * Handling for heterogeneous inflows. * Minor documentation updates. * Add tests for heterogeneous inflow feature. * Add exclusion for input data file to gitignore and add file to repo. * Another needed input excluded in gitignore... --- .gitignore | 5 +- README.md | 2 +- .../10_heterogeneous_wind/amr_input.inp | 163 ++++++++++++++ .../10_heterogeneous_wind/bash_script.sh | 23 ++ .../10_heterogeneous_wind/floris_runscript.py | 19 ++ .../floris_standin_data.csv | 202 ++++++++++++++++++ .../hercules_input_000.yaml | 61 ++++++ .../hercules_runscript_CLcontrol.py | 37 ++++ .../10_heterogeneous_wind/readme.txt | 4 + .../wind_power_reference_data.csv | 12 ++ hercules/floris_standin.py | 67 ++++++ tests/floris_standin_test.py | 86 ++++++++ tests/test_inputs/amr_standin_data_het.csv | 3 + 13 files changed, 681 insertions(+), 3 deletions(-) create mode 100644 example_case_folders/10_heterogeneous_wind/amr_input.inp create mode 100755 example_case_folders/10_heterogeneous_wind/bash_script.sh create mode 100644 example_case_folders/10_heterogeneous_wind/floris_runscript.py create mode 100644 example_case_folders/10_heterogeneous_wind/floris_standin_data.csv create mode 100644 example_case_folders/10_heterogeneous_wind/hercules_input_000.yaml create mode 100644 example_case_folders/10_heterogeneous_wind/hercules_runscript_CLcontrol.py create mode 100644 example_case_folders/10_heterogeneous_wind/readme.txt create mode 100644 example_case_folders/10_heterogeneous_wind/wind_power_reference_data.csv create mode 100644 tests/test_inputs/amr_standin_data_het.csv diff --git a/.gitignore b/.gitignore index 05f44eaf..f6223e58 100644 --- a/.gitignore +++ b/.gitignore @@ -34,8 +34,9 @@ hercules/local_amr_wind_demo/sample_copy.nc #Ignore csv files *.csv -!tests/test_inputs/amr_standin_data.csv -!tests/test_inputs/external_data.csv +!tests/test_inputs/*.csv +!example_case_folders/10_heterogeneous_wind/floris_standin_data.csv +!example_case_folders/10_heterogeneous_wind/wind_power_reference_data.csv # Some output files to ignore t_00* diff --git a/README.md b/README.md index 451c448c..c920a11e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # README # hercules -Hercules, based on the acronym "Hybrid Energy and Control Using Large Eddy Simulations", is an open-source tool for wind-based hybrid plant simulation in real time. Hercules is based around high fidelity wind farm flow simulations through AMR-Wind, co-simulated with a hybrid plant that includes solar, storage, and generation. The entire hybrid plant can be controlled using the Wind Hybrid Open Controller (WHOC). +Hercules, based on the acronym "Hybrid Energy and Control Using Large Eddy Simulations", is an open-source tool for wind-based hybrid plant simulation in real time. Hercules is based around high fidelity wind farm flow simulations through AMR-Wind, co-simulated with a hybrid plant that includes solar, storage, and generation. The entire hybrid plant can be controlled using the [Wind Hybrid Open Controller (WHOC)](https://github.com/nrel/wind-hybrid-open-controller). # Documentation diff --git a/example_case_folders/10_heterogeneous_wind/amr_input.inp b/example_case_folders/10_heterogeneous_wind/amr_input.inp new file mode 100644 index 00000000..8ef0cd1a --- /dev/null +++ b/example_case_folders/10_heterogeneous_wind/amr_input.inp @@ -0,0 +1,163 @@ +#¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨# +# SIMULATION STOP # +#.......................................# +time.stop_time = 100.0 # Max (simulated) time to evolve +time.max_step = -1 # Max number of time steps + +#¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨# +# TIME STEP COMPUTATION # +#.......................................# +time.fixed_dt = 0.5 # Use this constant dt if > 0 +time.cfl = 0.95 # CFL factor + +#¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨# +# INPUT AND OUTPUT # +#.......................................# +time.plot_interval = 3600 # Steps between plot files +time.checkpoint_interval = 3600 # Steps between checkpoint files +io.restart_file = "/projects/ssc/amr_precursors/b_abl_neutral_lowTI_redo/chk14400" + +#¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨# +# PHYSICS # +#.......................................# +incflo.gravity = 0. 0. -9.81 # Gravitational force (3D) +incflo.density = 1.0 # Reference density + +incflo.use_godunov = 1 +incflo.godunov_type = weno_z +incflo.diffusion_type = 1 +transport.viscosity = 1.0e-5 +transport.laminar_prandtl = 0.7 +transport.turbulent_prandtl = 0.3333 +turbulence.model = OneEqKsgsM84 + +incflo.physics = ABL Actuator +ICNS.source_terms = BoussinesqBuoyancy CoriolisForcing ABLMeanBoussinesq ActuatorForcing +TKE.source_terms = KsgsM84Src +BoussinesqBuoyancy.reference_temperature = 300.0 +CoriolisForcing.latitude = 41.3 +ABLForcing.abl_forcing_height = 90 +incflo.velocity = 6.928203230275509 4.0 0.0 + + +# Atmospheric boundary layer +ABL.temperature_heights = 0.0 700.0 800.0 1280.0 +ABL.temperature_values = 300.0 300.0 308.0 309.44 +ABL.reference_temperature = 300.0 +ABL.kappa = .40 +ABL.surface_roughness_z0 = 1.0E-4 +ABL.Uperiods = 25.0 +ABL.Vperiods = 25.0 +ABL.cutoff_height = 50.0 +ABL.deltaU = 1.0 +ABL.deltaV = 1.0 +ABL.normal_direction = 2 +ABL.perturb_ref_height = 50.0 +ABL.perturb_temperature = false +ABL.perturb_velocity = true +ABL.stats_output_format = netcdf +ABL.stats_output_frequency = 1 +ABL.surface_temp_flux = 0.00 +ABL.wall_shear_stress_type = "Moeng" + +ABL.bndry_file = "/projects/ssc/amr_precursors/b_abl_neutral_lowTI_redo/bndry_files" +ABL.bndry_io_mode = 1 +ABL.bndry_planes = ylo xlo # I'm (Paul) adding this but not sure if I have to +ABL.bndry_var_names = velocity temperature tke + + +# Output boundary files +ABL.bndry_planes = ylo xlo +ABL.bndry_output_start_time = 7200.0 +ABL.bndry_var_names = velocity temperature tke +ABL.bndry_output_format = native +ABL.stats_output_frequency = 1 +ABL.stats_output_format = netcdf + +# Whether to use helics +helics.activated = true +helics.broker_port = 32000 + +#¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨# +# ADAPTIVE MESH REFINEMENT # +#.......................................# +amr.n_cell = 512 512 128 # Grid cells at coarsest AMRlevel +amr.max_level = 0 # Max AMR level in hierarchy + +#¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨# +# GEOMETRY # +#.......................................# +geometry.prob_lo = 0. 0. 0. # Lo corner coordinates +geometry.prob_hi = 5120. 5120. 1280. # Hi corner coordinates +geometry.is_periodic = 0 0 0 +xlo.type = "mass_inflow" +xlo.density = 1.0 +xlo.temperature = 0.0 # value required but ignored +xlo.tke = 0.0 +xhi.type = "pressure_outflow" + +ylo.type = "mass_inflow" +ylo.density = 1.0 +ylo.temperature = 0.0 +ylo.tke = 0.0 +yhi.type = "pressure_outflow" + +# Boundary conditions +zlo.type = "wall_model" +zlo.tke_type = "zero_gradient" + +zhi.type = "slip_wall" +zhi.temperature_type = "fixed_gradient" +zhi.temperature = 0.003 # tracer is used to specify potential temperature gradient + +#¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨# +# VERBOSITY # +#.......................................# +incflo.verbose = 0 # incflo_level + + + +#¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨# +# SAMPLING # +#.......................................# +incflo.post_processing = samplingPlane samplingLine + +samplingPlane.output_frequency = 600 +samplingPlane.labels = z_plane +samplingPlane.fields = velocity temperature +samplingPlane.z_plane.type = PlaneSampler +samplingPlane.z_plane.axis1 = 5110 0.0 0.0 +samplingPlane.z_plane.axis2 = 0.0 5110 0.0 +samplingPlane.z_plane.origin = 5.0 5.0 0.0 +samplingPlane.z_plane.num_points = 512 512 +samplingPlane.z_plane.normal = 0.0 0.0 1.0 +samplingPlane.z_plane.offsets = 5.0 85.0 155.0 255.0 + + +samplingLine.output_frequency = 1 +samplingLine.labels = z_line +samplingLine.fields = velocity temperature +samplingLine.z_line.type = LineSampler +samplingLine.z_line.num_points = 128 +samplingLine.z_line.start = 5.0 5.0 5.0 +samplingLine.z_line.end = 5.0 5.0 1275.0 + +#¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨# +# TURBINES # +#.......................................# + +Actuator.type = UniformCtDisk +Actuator.UniformCtDisk.rotor_diameter = 126.0 +Actuator.UniformCtDisk.hub_height = 90.0 +Actuator.UniformCtDisk.thrust_coeff = 0.0 0.0 1.132034888 0.999470963 0.917697381 0.860849503 0.815371198 0.811614904 0.807939328 0.80443352 0.800993851 0.79768116 0.794529244 0.791495834 0.788560434 0.787217182 0.787127977 0.785839257 0.783812219 0.783568108 0.783328285 0.781194418 0.777292539 0.773464375 0.769690236 0.766001924 0.762348072 0.758760824 0.755242872 0.751792927 0.748434131 0.745113997 0.717806682 0.672204789 0.63831272 0.610176496 0.585456847 0.563222111 0.542912273 0.399312061 0.310517829 0.248633226 0.203543725 0.169616419 0.143478955 0.122938861 0.106515296 0.093026095 0.081648606 0.072197368 0.064388275 0.057782745 0.0 0.0 +Actuator.UniformCtDisk.wind_speed = 0.0 2.9 3.0 4.0 5.0 6.0 7.0 7.1 7.2 7.3 7.4 7.5 7.6 7.7 7.8 7.9 8.0 9.0 10.0 10.1 10.2 10.3 10.4 10.5 10.6 10.7 10.8 10.9 11.0 11.1 11.2 11.3 11.4 11.5 11.6 11.7 11.8 11.9 12.0 13.0 14.0 15.0 16.0 17.0 18.0 19.0 20.0 21.0 22.0 23.0 24.0 25.0 25.1 50.0 +Actuator.UniformCtDisk.epsilon = 10.0 +Actuator.UniformCtDisk.density = 1.225 +Actuator.UniformCtDisk.diameters_to_sample = 1.0 +Actuator.UniformCtDisk.num_points_r = 20 +Actuator.UniformCtDisk.num_points_t = 5 + + +Actuator.labels = T00 T01 +Actuator.T00.base_position = 0.0 0.0 0.0 +Actuator.T01.base_position = 1000.0 0.0 0.0 diff --git a/example_case_folders/10_heterogeneous_wind/bash_script.sh b/example_case_folders/10_heterogeneous_wind/bash_script.sh new file mode 100755 index 00000000..209a0241 --- /dev/null +++ b/example_case_folders/10_heterogeneous_wind/bash_script.sh @@ -0,0 +1,23 @@ +# Example bash for running things locally +# I just run these one at a t time + +# A lot of modules and conda stuff +conda activate hercules + +# Set the helics port to use: +export HELICS_PORT=32000 + +#make sure you use the same port number in the amr_input.inp and hercules_input_000.yaml files. + +# Remove any existing outputs/ folder +if [ -d "outputs" ]; then + rm -f outputs/*.log +fi + +# Create the outputs/ folder +mkdir -p outputs + +# Wait for the open-loop control simulation to finish and then run the closed-loop simulation +helics_broker -t zmq -f 2 --loglevel="debug" --local_port=$HELICS_PORT & +python3 hercules_runscript_CLcontrol.py hercules_input_000.yaml >> outputs/hercules.log 2>&1 & +python3 floris_runscript.py amr_input.inp floris_standin_data.csv >> outputs/floris.log 2>&1 diff --git a/example_case_folders/10_heterogeneous_wind/floris_runscript.py b/example_case_folders/10_heterogeneous_wind/floris_runscript.py new file mode 100644 index 00000000..5a43afb5 --- /dev/null +++ b/example_case_folders/10_heterogeneous_wind/floris_runscript.py @@ -0,0 +1,19 @@ +import sys + +from hercules.floris_standin import launch_floris + +# Check that one command line argument was given +if len(sys.argv) < 2: + raise Exception("Usage: python floris_runscript.py ") + +# # Get the first command line argument +# This is the name of the file to read +amr_input_file = sys.argv[1] +print(f"Running FLORIS standin with input file: {amr_input_file}") +if len(sys.argv) > 2: + amr_standin_data_file = sys.argv[2] + print(f"Using standin data for AMR-Wind from file: {amr_standin_data_file}") +else: + amr_standin_data_file = None + +launch_floris(amr_input_file, amr_standin_data_file) diff --git a/example_case_folders/10_heterogeneous_wind/floris_standin_data.csv b/example_case_folders/10_heterogeneous_wind/floris_standin_data.csv new file mode 100644 index 00000000..9d3da213 --- /dev/null +++ b/example_case_folders/10_heterogeneous_wind/floris_standin_data.csv @@ -0,0 +1,202 @@ +,time,amr_wind_speed,amr_wind_direction,heterogeneous_inflow_config +0,0.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +1,0.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +2,1.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +3,1.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +4,2.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +5,2.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +6,3.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +7,3.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +8,4.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +9,4.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +10,5.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +11,5.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +12,6.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +13,6.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +14,7.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +15,7.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +16,8.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +17,8.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +18,9.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +19,9.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +20,10.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +21,10.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +22,11.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +23,11.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +24,12.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +25,12.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +26,13.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +27,13.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +28,14.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +29,14.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +30,15.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +31,15.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +32,16.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +33,16.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +34,17.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +35,17.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +36,18.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +37,18.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +38,19.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +39,19.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +40,20.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +41,20.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +42,21.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +43,21.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +44,22.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +45,22.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +46,23.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +47,23.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +48,24.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +49,24.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +50,25.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +51,25.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +52,26.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +53,26.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +54,27.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +55,27.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +56,28.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +57,28.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +58,29.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +59,29.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +60,30.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +61,30.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +62,31.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +63,31.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +64,32.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +65,32.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +66,33.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +67,33.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +68,34.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +69,34.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +70,35.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +71,35.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +72,36.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +73,36.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +74,37.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +75,37.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +76,38.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +77,38.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +78,39.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +79,39.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +80,40.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +81,40.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +82,41.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +83,41.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +84,42.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +85,42.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +86,43.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +87,43.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +88,44.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +89,44.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +90,45.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +91,45.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +92,46.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +93,46.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +94,47.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +95,47.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +96,48.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +97,48.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +98,49.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +99,49.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +100,50.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +101,50.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +102,51.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +103,51.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +104,52.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +105,52.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +106,53.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +107,53.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +108,54.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +109,54.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +110,55.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +111,55.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +112,56.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +113,56.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +114,57.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +115,57.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +116,58.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +117,58.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +118,59.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +119,59.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +120,60.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +121,60.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +122,61.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +123,61.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +124,62.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +125,62.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +126,63.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +127,63.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +128,64.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +129,64.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +130,65.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +131,65.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +132,66.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +133,66.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +134,67.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +135,67.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +136,68.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +137,68.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +138,69.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +139,69.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +140,70.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +141,70.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +142,71.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +143,71.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +144,72.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +145,72.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +146,73.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +147,73.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +148,74.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +149,74.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +150,75.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +151,75.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +152,76.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +153,76.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +154,77.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +155,77.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +156,78.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +157,78.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +158,79.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +159,79.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +160,80.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +161,80.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +162,81.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +163,81.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +164,82.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +165,82.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +166,83.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +167,83.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +168,84.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +169,84.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +170,85.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +171,85.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +172,86.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +173,86.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +174,87.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +175,87.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +176,88.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +177,88.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +178,89.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +179,89.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +180,90.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +181,90.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +182,91.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +183,91.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +184,92.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +185,92.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.95, 0.95, 1.0]]}" +186,93.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.98, 0.98, 1.0]]}" +187,93.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.98, 0.98, 1.0]]}" +188,94.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.98, 0.98, 1.0]]}" +189,94.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.98, 0.98, 1.0]]}" +190,95.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.98, 0.98, 1.0]]}" +191,95.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.98, 0.98, 1.0]]}" +192,96.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.98, 0.98, 1.0]]}" +193,96.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.98, 0.98, 1.0]]}" +194,97.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.98, 0.98, 1.0]]}" +195,97.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.98, 0.98, 1.0]]}" +196,98.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.98, 0.98, 1.0]]}" +197,98.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.98, 0.98, 1.0]]}" +198,99.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.98, 0.98, 1.0]]}" +199,99.5,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.98, 0.98, 1.0]]}" +200,100.0,8.0,270.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.9, 0.98, 0.98, 1.0]]}" \ No newline at end of file diff --git a/example_case_folders/10_heterogeneous_wind/hercules_input_000.yaml b/example_case_folders/10_heterogeneous_wind/hercules_input_000.yaml new file mode 100644 index 00000000..58f0db26 --- /dev/null +++ b/example_case_folders/10_heterogeneous_wind/hercules_input_000.yaml @@ -0,0 +1,61 @@ +# Input YAML for emy_python + +# Name +name: example_000 + +### +# Describe this emulator setup +description: Just a solar plant + +dt: 0.5 + +hercules_comms: + + amr_wind: + + wind_farm_0: + type: amr_wind_local #options are amr_wind or amr_wind_local + amr_wind_input_file: amr_input.inp + yaw_simulator_name: yaw_system_0 # can also use "none" (without quotes) + + helics: + + config: + name: hercules # What is the purpose of this name + use_dash_frontend: False + KAFKA: False + KAFKA_topics: EMUV1py + helics: + # deltat: 1 # This will be assigned in software + subscription_topics: [status] + publication_topics: [control] + endpoints: [] + helicsport : 32000 + publication_interval: 1 + endpoint_interval: 1 + starttime: 0 + stoptime: 100 + + Agent: ControlCenter + +py_sims: + + solar_farm_0: # The name of py_sim object 1 + + py_sim_type: SimpleSolar + capacity: 50 # MW + efficiency: 0.5 #Fraction + + initial_conditions: + + power: 25 # MW + irradiance: 1000 + +controller: + + controller_type: # This is not needed + num_turbines: 2 # Should match AMR-Wind! Ideally, would come from AMR-wind + initial_conditions: + yaw: 270. # degrees (same for all turbines) (will this work?) + +external_data_file: wind_power_reference_data.csv diff --git a/example_case_folders/10_heterogeneous_wind/hercules_runscript_CLcontrol.py b/example_case_folders/10_heterogeneous_wind/hercules_runscript_CLcontrol.py new file mode 100644 index 00000000..c5586e93 --- /dev/null +++ b/example_case_folders/10_heterogeneous_wind/hercules_runscript_CLcontrol.py @@ -0,0 +1,37 @@ +# Copyright 2021 NREL + +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# See https://nrel.github.io/wind-hybrid-open-controller for documentation + +import sys + +from hercules.emulator import Emulator +from hercules.py_sims import PySims +from hercules.utilities import load_yaml +from whoc.controllers.wind_farm_power_tracking_controller import WindFarmPowerTrackingController +from whoc.interfaces.hercules_actuator_disk_interface import HerculesADInterface + +input_dict = load_yaml(sys.argv[1]) +input_dict["output_file"] = "hercules_output_cl.csv" + +interface = HerculesADInterface(input_dict) + +print("Running closed-loop controller...") +controller = WindFarmPowerTrackingController(interface, input_dict) + +py_sims = PySims(input_dict) + +emulator = Emulator(controller, py_sims, input_dict) +emulator.run_helics_setup() +emulator.enter_execution(function_targets=[], function_arguments=[[]]) + +print("Finished running closed-loop controller.") \ No newline at end of file diff --git a/example_case_folders/10_heterogeneous_wind/readme.txt b/example_case_folders/10_heterogeneous_wind/readme.txt new file mode 100644 index 00000000..6bb8116b --- /dev/null +++ b/example_case_folders/10_heterogeneous_wind/readme.txt @@ -0,0 +1,4 @@ +This example runs a wind farm level power tracking controller, using FLORIS as a simulation +testbed, with heterogeneous inflow. The control algorithm is based on the A2e2g project. +To run the example, run bash_script.sh. This will the closed loop power +tracking controller. diff --git a/example_case_folders/10_heterogeneous_wind/wind_power_reference_data.csv b/example_case_folders/10_heterogeneous_wind/wind_power_reference_data.csv new file mode 100644 index 00000000..5472901a --- /dev/null +++ b/example_case_folders/10_heterogeneous_wind/wind_power_reference_data.csv @@ -0,0 +1,12 @@ +time,wind_power_reference +0.0,4000.0 +10.0,4000.0 +20.0,3000.0 +30.0,3000.0 +39.0,3000.0 +40.0,2000.0 +50.0,2000.0 +59.0,2000.0 +60.0,3000.0 +70.0,3000.0 +80.0,4000.0 \ No newline at end of file diff --git a/hercules/floris_standin.py b/hercules/floris_standin.py index 98e94cf1..1faff36f 100644 --- a/hercules/floris_standin.py +++ b/hercules/floris_standin.py @@ -24,11 +24,13 @@ import logging import sys +import warnings from pathlib import Path import numpy as np from floris import FlorisModel from floris.turbine_library import build_cosine_loss_turbine_dict +from scipy.interpolate import interp1d from hercules.amr_wind_standin import AMRWindStandin, read_amr_wind_input @@ -202,9 +204,32 @@ def get_step(self, sim_time_s, yaw_angles=None, power_setpoints=None): self.standin_data["amr_wind_direction"], ) + if "heterogeneous_inflow_config" in self.standin_data.columns: + if sim_time_s < self.standin_data["time"].iloc[-1]: + next_idx = ( + self.standin_data["time"][self.standin_data["time"] > sim_time_s].idxmin() + ) + else: + next_idx = len(self.standin_data) - 1 + prev_idx = next_idx - 1 + + prev_dict = eval(self.standin_data["heterogeneous_inflow_config"].iloc[prev_idx]) + next_dict = eval(self.standin_data["heterogeneous_inflow_config"].iloc[next_idx]) + + heterogeneous_inflow_config = self.interpolate_heterogeneous_inflow_config( + prev_dict, + next_dict, + self.standin_data["time"].iloc[prev_idx], + self.standin_data["time"].iloc[next_idx], + sim_time_s + ) + else: # No heterogeneous data supplied + heterogeneous_inflow_config = None + else: amr_wind_speed = 8.0 amr_wind_direction = 240.0 + heterogeneous_inflow_config = None turbine_wind_directions = [amr_wind_direction] * self.num_turbines @@ -260,6 +285,7 @@ def get_step(self, sim_time_s, yaw_angles=None, power_setpoints=None): self.fmodel.set( wind_speeds=[amr_wind_speed], wind_directions=[amr_wind_direction], + heterogeneous_inflow_config=heterogeneous_inflow_config, yaw_angles=yaw_misalignments, power_setpoints=power_setpoints ) @@ -294,6 +320,47 @@ def process_periodic_publication(self): def process_subscription_messages(self, msg): pass + @staticmethod + def interpolate_heterogeneous_inflow_config(dict_0, dict_1, time_0, time_1, time): + # Check for valid keys; if not there, raise warning and return None + default_dist = 1.0e6 + default_heterogeneous_inflow_config = { + "x": np.array([-default_dist, -default_dist, default_dist, default_dist]), + "y": np.array([-default_dist, default_dist, -default_dist, default_dist]), + "speed_multipliers": np.array([[1.0, 1.0, 1.0, 1.0]]), + } + for k in ["x", "y", "speed_multipliers"]: + if (k not in dict_0.keys()) or (k not in dict_1.keys()): + warnings.warn(( + f"Needed key '{k}' missing from heterogeneous_inflow_config."+ + " Proceeding with homogeneous inflow." + )) + return default_heterogeneous_inflow_config + + # Check that x and y are the same between time stamps + # (changing x, y not currently supported) + if (dict_0["x"] != dict_1["x"]) or (dict_0["y"] != dict_1["y"]): + warnings.warn(( + "Changing x, y between time stamps not currently supported."+ + " Proceeding with homogeneous inflow." + )) + return default_heterogeneous_inflow_config + + # Interpolate speed multipliers + sm_interpolator = interp1d( + [time_0, time_1], + [dict_0["speed_multipliers"][0], dict_1["speed_multipliers"][0]], + axis=0, + ) + speed_multipliers = sm_interpolator(time) + + # Create return dictionary + return { + "x": np.array(dict_0["x"]), + "y": np.array(dict_0["y"]), + "speed_multipliers": np.array([speed_multipliers]) + } + def launch_floris(amr_input_file, amr_standin_data_file=None): temp = read_amr_wind_input(amr_input_file) diff --git a/tests/floris_standin_test.py b/tests/floris_standin_test.py index ce2e5547..9dc2897f 100644 --- a/tests/floris_standin_test.py +++ b/tests/floris_standin_test.py @@ -2,6 +2,7 @@ from pathlib import Path import numpy as np +import pandas as pd from floris import FlorisModel from hercules.amr_wind_standin import AMRWindStandin from hercules.floris_standin import ( @@ -13,6 +14,7 @@ AMR_INPUT = Path(__file__).resolve().parent / "test_inputs" / "amr_input_florisstandin.inp" AMR_EXTERNAL_DATA = Path(__file__).resolve().parent / "test_inputs" / "amr_standin_data.csv" +AMR_EXTERNAL_DATA_HET = Path(__file__).resolve().parent / "test_inputs" / "amr_standin_data_het.csv" CONFIG = { "name": "floris_standin", @@ -300,3 +302,87 @@ def test_FlorisStandin_smoothing_coefficient(): # Check magnitude is correct assert np.allclose(0.1*np.array(fs_tp_no_smoothing), np.array(fs_tp_heavy_smoothing)) + +def test_FlorisStandin_with_heterogeneous_inflow(): + floris_standin = FlorisStandin( + CONFIG, + AMR_INPUT, + AMR_EXTERNAL_DATA_HET, + smoothing_coefficient=0.0 + ) + + # Set the wind shear to zero to simplify comparisons + floris_standin.fmodel.set(wind_shear=0.0) + + # Check heterogeneous map interpolation at specified time + floris_standin.get_step(0) + turbine_vels_norm_0 = floris_standin.fmodel.turbine_average_velocities / 8.0 + assert np.allclose(turbine_vels_norm_0[0,0], 0.9) + assert 0.9 < turbine_vels_norm_0[0,1] < 1.0 # Unwaked at 0 degrees + # Check matches data at time 0 + + # Check changed correctly at time 4 + floris_standin.get_step(4) + turbine_vels_norm_4 = floris_standin.fmodel.turbine_average_velocities / 8.0 + assert np.allclose(turbine_vels_norm_4[0,:], 1.0) + + # Check interpolates correctly for time step 2. + floris_standin.get_step(2) + turbine_vels_norm_2 = floris_standin.fmodel.turbine_average_velocities / 8.0 + assert (turbine_vels_norm_0 < turbine_vels_norm_2).all() + assert (turbine_vels_norm_2 < turbine_vels_norm_4).all() + +def test_FlorisStandin_with_bad_heterogeneous_inflow(): + # Read in valid heterogeneous inflow data + df_het_orig = pd.read_csv(AMR_EXTERNAL_DATA_HET) + + # Add extra key to heterogeneous_inflow_config; reprint + for i in range(len(df_het_orig)): + dict_temp = eval(df_het_orig.heterogeneous_inflow_config.iloc[i]) + dict_temp["bad_key"] = 0.0 + df_het_orig.heterogeneous_inflow_config.iloc[i] = str(dict_temp) + + # Write to new file + AMR_EXTERNAL_DATA_HET_BAD = ( + Path(__file__).resolve().parent / "test_inputs" / "amr_standin_data_het_bad.csv" + ) + df_het_orig.to_csv(AMR_EXTERNAL_DATA_HET_BAD, index=False) + + # Check that the extra key is just ignored + floris_standin = FlorisStandin( + CONFIG, + AMR_INPUT, + AMR_EXTERNAL_DATA_HET_BAD, + smoothing_coefficient=0.0 + ) + + # Set the wind shear to zero to simplify comparisons + floris_standin.fmodel.set(wind_shear=0.0) + + # Step, check ok + floris_standin.get_step(0) + turbine_vels_norm_0 = floris_standin.fmodel.turbine_average_velocities / 8.0 + assert np.allclose(turbine_vels_norm_0[0,0], 0.9) + + # Remove needed key for heterogeneous_inflow_config + df_het_orig = pd.read_csv(AMR_EXTERNAL_DATA_HET) + for i in range(len(df_het_orig)): + dict_temp = eval(df_het_orig.heterogeneous_inflow_config.iloc[i]) + del dict_temp["speed_multipliers"] + df_het_orig.heterogeneous_inflow_config.iloc[i] = str(dict_temp) + df_het_orig.to_csv(AMR_EXTERNAL_DATA_HET_BAD, index=False) + + # Check proceeds without heterogeneity + floris_standin = FlorisStandin( + CONFIG, + AMR_INPUT, + AMR_EXTERNAL_DATA_HET_BAD, + smoothing_coefficient=0.0 + ) + floris_standin.fmodel.set(wind_shear=0.0) + floris_standin.get_step(0) + turbine_vels_norm_0 = floris_standin.fmodel.turbine_average_velocities / 8.0 + assert np.allclose(turbine_vels_norm_0, 1.0) # No heterogeneity applied + + # Delete the bad file + AMR_EXTERNAL_DATA_HET_BAD.unlink() diff --git a/tests/test_inputs/amr_standin_data_het.csv b/tests/test_inputs/amr_standin_data_het.csv new file mode 100644 index 00000000..63107a58 --- /dev/null +++ b/tests/test_inputs/amr_standin_data_het.csv @@ -0,0 +1,3 @@ +,time,amr_wind_speed,amr_wind_direction,heterogeneous_inflow_config +0,0.0,8.0,0.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[0.8, 0.9, 0.9, 1.0]]}" +1,4.0,8.0,0.0,"{'x': [-2000.0, -2000.0, 2000.0, 2000.0], 'y': [-2000.0, 2000.0, -2000.0, 2000.0], 'speed_multipliers': [[1.0, 1.0, 1.0, 1.0]]}" \ No newline at end of file