Flicker Modeling
** Shadow functions **
- hopp.simulation.technologies.layout.shadow_flicker.get_time_zone(lat: float, lon: float) <module 'pytz.tzinfo' from '/home/docs/checkouts/readthedocs.org/user_builds/hopp/envs/v3.1/lib/python3.10/site-packages/pytz/tzinfo.py'>
- hopp.simulation.technologies.layout.shadow_flicker.get_sun_pos(lat: float, lon: float, step_in_minutes: float = 60, n: int = 8760, start_hr: int = 0, steps: range | None = None) Tuple[ndarray, ndarray, list]
Calculates the sun azimuth & elevation angles at each time step in provided range
- Parameters:
lat – latitude, degrees
lon – longitude, degrees
step_in_minutes – the number of minutes between each step
n – number of steps
start_hr – hour of first day of the year
steps – if given, calculate for the timesteps in the range, ignoring start_hr and n
- Returns:
array of sun azimuth, array of sun elevation, datetime of each entry
- hopp.simulation.technologies.layout.shadow_flicker.blade_pos_of_rotated_ellipse(radius_x: float, radius_y: float, rotation_theta: float | ndarray, blade_theta: float | ndarray, center_x: float, center_y: float) Tuple[float, float]
Parametric equation for rotated ellipse
- Parameters:
radius_x – radius of ellipse along x-axis
radius_y – radius of ellipse along y-axis
rotation_theta – rotation of ellipse in radians
blade_theta – angle of blade in radians
center_x – ellipse center x coordinate
center_y – ellipse center y coordinate
- Returns:
(x, y) coordinate of the blade tip along rotated ellipse
- hopp.simulation.technologies.layout.shadow_flicker.get_turbine_shadow_polygons(blade_length: float, blade_angle: float | None, azi_ang: float, elv_ang: float, wind_dir, tower_shadow: bool = True, tower_height: float | None = None) Tuple[None | Polygon | MultiPolygon, float]
Calculates the (x, y) coordinates of a wind turbine’s shadow, which depends on the sun azimuth and elevation.
The dimensions of the tower and blades are in fixed ratios to the blade_length. The blade angle is the degrees from z-axis, whereas the wind direction is where the turbine is pointing towards (if None, north is assumed).
In spherical coordinates, blade angle is phi and wind direction is theta, with 0 at north, moving clockwise.
The output shadow polygon is relative to the turbine located at (0, 0).
- Parameters:
blade_length – meters, radius in spherical coords
blade_angle – degrees from z-axis, or None to use ellipse as swept area
azi_ang – azimuth degrees, clockwise from north as 0
elv_ang – elevation degrees, from x-y plane as 0
wind_dir – degrees from north, clockwise, determines which direction rotor is facing
tower_shadow – if false, do not include the tower’s shadow
- Returns:
(shadow polygon, shadow angle from north) if shadow exists, otherwise (None, None)
- hopp.simulation.technologies.layout.shadow_flicker.get_turbine_shadows_timeseries(blade_length: float, steps: range, angles_per_step: int, azi_ang: list | ndarray, elv_ang: list | ndarray, wind_ang: list | None = None, tower_shadow: bool = True) List[List[None | Polygon | MultiPolygon]]
Calculate turbine shadows for a number of equally-spaced blade angles per time step. Returns a list of turbine shadows per time step, where each entry has a shadow for each angle.
- Parameters:
blade_length – meters
steps – which timesteps to calculate
angles_per_step – number of blade angles per timestep
elv_ang – array of elevation angles, degrees
azi_ang – array of azimuth angles, degrees
wind_ang – array of wind direction degrees with 0 as north, degrees
tower_shadow – if false, do not include the tower’s shadow
- Returns:
list of turbine shadows per time step
- hopp.simulation.technologies.layout.shadow_flicker.shadow_cast_over_panel(panel_x: float, panel_y: float, n_mod: int, blade_length: float, blade_angle: float, azi_ang: float, elv_ang: float, wind_dir: float | None = None) Tuple[ndarray, Polygon] | None
Calculates which cells in a string of PV panels are shaded. The panel is located at a (panel_x, panel_y) distance from the turbine at (0, 0). Shadow shape depends on the sun azimuth and elevation angle.
The PV panel is assumed to be a 96-cell, 1.488 x 0.992 m panel with 12.4 x 12.4 cm cells, 12x8 cells with 2, 4, and 2 columns of cells per diode for total of 3 substrings.
Turbine dimensions depend on blade_length and shape of the blades depend on blade_angle and wind_dir– see get_turbine_shadow_polygons for more details.
- Parameters:
panel_x – distance from turbine to bottom-left corner of panels
panel_y – degrees from x-axis to bottom-left corner of panels
n_mod – number of modules in a string ( n x 1 solar array)
blade_length – meters, radius in spherical coords
blade_angle – degrees from xv-plane, 90-inclination/theta in spherical coords
azi_ang – azimuth degrees
elv_ang – elevation degrees
wind_dir – degrees from north, clockwise, determines which dir rotor is facing, azimuth/phi in spherical coord
- Returns:
grid of cells where 1 means shaded, turbine shadow polygon
- hopp.simulation.technologies.layout.shadow_flicker.create_turbines_in_grid(dx: float, dy: float, theta: float | ndarray, n_turbines_per_side: int) Tuple[list, Polygon]
Sets up turbines in a grid. Returns a list of the turbine positions and a Polygon including them.
- Parameters:
dx – x distance between turbines in grid
dy – y distance
theta – rotation of grid
n_turbines_per_side
- Returns:
- hopp.simulation.technologies.layout.shadow_flicker.get_turbine_grid_shadow(shadow_polygons: MultiPolygon | None, turb_pos: list) List[Polygon | MultiPolygon] | None
Calculate shadow polygons for each step in simulation for each turbine in the grid
- Returns:
list with dimension [step_per_hour, angles_per_step]
- hopp.simulation.technologies.layout.shadow_flicker.create_module_cells_mesh(mod_x: float, mod_y: float, mod_width: float, mod_height: float, n_module: int)
For a string of PV modules, create an array of meshgrids having a point for each cell.
- Parameters:
mod_x – x coordinate of corner of panel
mod_y – y coordinate
mod_width – single module’s width
mod_height – module’s height
n_module – number of modules per string
- Returns:
n_module array of meshgrids
- hopp.simulation.technologies.layout.shadow_flicker.shadow_over_module_cells(module_mesh: ndarray, turbine_shadow: Polygon | MultiPolygon)
For a meshgrid where each point is a cell in a PV module, identify which cells are in the turbine_shadow.
- Parameters:
module_mesh – meshgrid
turbine_shadow – polygon
- Returns:
2-D array with same coordinates as the PV module with values 0 (unshaded) or 1 (shaded)
- hopp.simulation.technologies.layout.shadow_flicker.create_pv_string_points(x_coord: float, y_coord: float, mod_width: float, mod_height: float, string_width: float, string_height: float) Tuple[Polygon, ndarray]
- Parameters:
x_coord
y_coord
mod_width
mod_height
string_width
string_height
- Returns:
** PV assumptions **
- hopp.simulation.technologies.layout.pv_module.spe_power(spe_eff_level, spe_rad_level, spe_area) float
Computes the module power per the SPE model
- hopp.simulation.technologies.layout.pv_module.get_module_attribs(model: Pvwattsv8 | Pvsamv1 | dict, only_ref_vals=True) dict
Returns the module attributes for either the PVsamv1 or PVWattsv8 models, see: https://nrel-pysam.readthedocs.io/en/main/modules/Pvsamv1.html#module-group
- Parameters:
model – PVsamv1 or PVWattsv8 model or parameter dictionary
only_ref_vals – if True, only return the reference values (e.g., I_sc_ref)
- Returns:
dict, with keys (if only_ref_values is True, otherwise will include all model-specific parameters): area [m2] aspect_ratio [-] length [m] I_mp_ref [A] I_sc_ref [A] P_mp_ref [kW] V_mp_ref [V] V_oc_ref [V] width [m]
- hopp.simulation.technologies.layout.pv_module.set_module_attribs(model: Pvwattsv8 | Pvsamv1, params: dict)
Sets the module model parameters for either the PVsamv1 or PVWattsv8 models. Will raise exception if not all required parameters are provided.
- Parameters:
model – PVWattsv8 or PVsamv1 model
params – dictionary of parameters
** Flicker models **
- class hopp.simulation.technologies.layout.flicker_mismatch.FlickerMismatch(lat: float, lon: float, angles_per_step: int | None = 1, blade_length: int = 35, solar_resource_data: dict | None = None, wind_dir: list | None = None, gridcell_width: float = 1.488, gridcell_height: float = 0.992, gridcells_per_string: int = 10)
Bases:
objectSimulates a wind turbine’s flicker over a grid for a given location. The shadow cast by the tower and the three blades are calculated for each of the simulation steps: number of blade angles (evenly spaced) per step of the hour.
The turbine is located at (0, 0) and a set of 2D arrays give the flicker losses at grid cell / coordinate. This ‘heatmap’ can have variable length and width, determined by ‘diam_mult_nwe’ and ‘diam_mult_s’, and can be normalized in several ways:
The ‘poa’ heat map is produced as a loss ratio relative to unshaded areas (0 - 1). This loss ratio is with respect to plane-of-array irradiance, as calculated for a single-axis tracking system using PVWattsv8.
The ‘power’ heat map is another loss ratio (0 - 1), but with respect to power production of an unshaded string of panels as modeled by PVMismatch. This is calculated by modeling panels at each grid location, grouped into strings, and simulating the power of each string.
The ‘time’ heat map is weighted by the number of timesteps each grid cell is shaded over the total timesteps simulated.
All heat maps are normalized by the number of timesteps simulated.
- Variables:
n_hours – number of hours in year
steps_per_hour – number of time steps to run each hour
diam_mult_nwe – in number of turbine diameters, the distance of the heat map’s north, west and east end from the turbine at (0, 0)
diam_mult_s – similarly, the number of turbine diameters the heatmap extends from (0, 0) south
periodic – if true, then the top of the heatmap continues onto the bottom, and vice versa for the east / west
turbine_tower_shadow – if true, then include the tower shadow
- n_hours: int = 8760
- steps_per_hour: int = 1
- diam_mult_nwe: int = 8
- diam_mult_s: int = 4
- periodic: bool = False
- turbine_tower_shadow: bool = True
- __init__(lat: float, lon: float, angles_per_step: int | None = 1, blade_length: int = 35, solar_resource_data: dict | None = None, wind_dir: list | None = None, gridcell_width: float = 1.488, gridcell_height: float = 0.992, gridcells_per_string: int = 10) None
Setup file output paths, the solar panel array, and the heat map template.
Also load irradiance and turbine shadow data.
- Parameters:
lat – latitude
lon – longitude
blade_length – meters
angles_per_step – number of blade angles to simulate every timestep
solar_resource_data – PySAM’s solar resource data: https://github.com/NREL/pysam/blob/master/files/ResourceTools.py
wind_dir – wind direction degrees, 0 as north, time series of len(8760 * steps_per_hour)
gridcell_width – grid cells of the heat map dimension
gridcell_height – grid cells of the heat map dimension
gridcells_per_string – for ‘poa’ heatmaps
- _create_pool(n_procs: int) Pool
Initialize a multiprocessing pool where each simulation step can be partitioned (by modulo operator) to split up work among different FlickerMismatch instances. :param n_procs:
- _setup_wind_dir(wind_dir_degrees)
- _setup_irradiance()
Compute solar azimuth and elevation degrees; Compute plane-of-array irradiance for a single-axis tracking PVwatts system :return:
- static get_turb_site(diam: int) Polygon
Return a polygon with the dimensions of the grid
- static _setup_heatmap_template(bounds: list, gridcell_width: float = 1.488, gridcell_height: float = 0.992) tuple
Create the points where each panel is located and the heat map grid template :param bounds: [min x, min y, max x, max y] of the grid :param gridcell_width: width of cells in the heat map :param gridcell_height: height of cells in the heat map :return: MultiPoint of panel locations, (heat map grid, x coordinates, y coordinates)
- static get_turb_pos_indices(heat_map_template: ndarray) tuple
Get the indices for the heat map template of the cell where the turbine is located
- _setup_array() None
Setup the solar panel array within the grid as a Point per panel
- _setup_string_points(array_points: Point | MultiPoint) list
Divide up the array of solar panels into strings. If FlickerMismatch.periodic, then a string can continue from the bottom edge of the grid back to the top, rather than running off the grid entirely.
- Parameters:
array_points
- Returns:
a list of which points belong in which string, dim [n_string, FlickerMismatch.modules_per_string]
- static _calculate_shading(weight: float, shadows: list, site_points: MultiPoint, heat_map: ndarray, gridcell_width: float, gridcell_height: float, normalize_by_area=False) None
Update the heat_map with shading losses in POA irradiance
- Parameters:
weight – loss to apply to shaded cells
shadows – list of shadow (Multi)Polygons for each blade angle
site_points – points of solar panels
heat_map – array with shading losses
gridcell_width – width of cells in the heat map
gridcell_height – height of cells in the heat map
normalize_by_area – if True, normalize weight per cell by how much area is shaded
- static _calculate_power_loss(poa: float, elv_ang: float, shadows: list, array_points: list, heat_map_flicker: ndarray, gridcell_width: float, gridcell_height: float, xs_min: float, ys_min: float, poa_shading_ratio: float = 0.9)
Update the heat map with flicker losses, using an unshaded string as baseline for normalizing
- Parameters:
poa – irradiance
elv_ang – solar elevation degree
shadows – list of shadow (Multi)Polygons for each blade angle
array_points – list of solar panels, [# strands, # strings per strand, FlickerMismatch.modules_per_string]
heat_map_flicker – array with flicker losses
gridcell_width – width of cells in the heat map
gridcell_height – height of cells in the heat map
xs_min – min of heat map grid’s x coordinates
ys_min – min of heat map grid’s y coordinates
poa_shading_ratio – how much of the poa is blocked by the shadow
- _calculate_turbine_shadow(ind: int) List[None | Polygon | MultiPolygon]
- create_heat_maps(steps: range, weight_option: tuple) tuple
Create shadow and flicker heat maps for a given range of simulation steps
- Parameters:
weight_option – tuple of selected weighting options, producing a heatmap each - “poa”: weight by plane-of-array irradiance - “power”: weight by power loss of pvmismatch module - “time”: weight by number of timesteps shaded
steps – which steps to run, must be within range calculated by steps_per_hour x angles_per_step
- Returns:
shadow heat map, flicker heat map
- run_parallel(n_procs: int, weight_option: tuple, intervals: Sequence[range] | None = None)
Runs create_heat_maps_irradiance in parallel
- Parameters:
n_procs
weight_option – tuple of selected weighting options, producing a heatmap each - “poa”: weight by plane-of-array irradiance - “power”: weight by power loss of pvmismatch module - “time”: weight by number of timesteps shaded
intervals – list of ranges to simulate; if none, simulate entire weather file’s records
- Returns:
heat_map_shadow, heat_map_flicker
- plot_on_site(plot_array=True, plot_points=True)