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: object

Simulates 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)