Beispiel #1
0
    def _setup_array(self) -> None:
        """
        Setup the solar panel array within the grid as a Point per panel
        """
        self.site = FlickerMismatch.get_turb_site(self.blade_length * 2)
        self.site_points, self.heat_map_template = self._setup_heatmap_template(
            self.site.bounds, self.gridcell_width, self.gridcell_height)

        min_y, max_y = self.site.bounds[1], self.site.bounds[3]
        string, string_points = create_pv_string_points(
            0, min_y, self.gridcell_width, self.gridcell_height,
            self.gridcell_width, max_y - min_y)

        # where solar strings are
        self.array = []
        x_pos = self.site.bounds[0]
        while x_pos < xs[-1]:
            tmp_string = translate(string, x_pos, 0)
            self.array.append(tmp_string)
            x_pos += self.gridcell_width
        logger.info("setup_turbines_and_arrays success")

        # Create points centered on each module
        self.array_string_points = []
        x_pos = self.site.bounds[0]
        while x_pos < xs[-1]:
            array_points = translate(string_points, x_pos, 0)
            self.array_string_points.append(
                self._setup_string_points(array_points))
            x_pos += self.gridcell_width

        logger.info("setup_point_maps success")
Beispiel #2
0
    def _setup_irradiance(self):
        """
        Compute solar azimuth and elevation degrees;
        Compute plane-of-array irradiance for a single-axis tracking PVwatts system
        :return:
        """
        pv_model = pv.default("PVWattsNone")
        pv_model.SystemDesign.array_type = 2
        pv_model.SystemDesign.gcr = .1
        if self.solar_resource_data is None:
            filename = str(self.lat) + "_" + str(
                self.lon) + "_psmv3_60_2012.csv"
            weather_path = Path(
                __file__
            ).parent.parent.parent / "resource_files" / "solar" / filename
            if not weather_path.is_file():
                SolarResource(self.lat, self.lon, year=2012)
                if not weather_path.is_file():
                    raise ValueError("resource file does not exist")
            pv_model.SolarResource.solar_resource_file = str(weather_path)
        else:
            pv_model.SolarResource.solar_resource_data = self.solar_resource_data
        pv_model.execute(0)
        self.poa = np.array(pv_model.Outputs.poa)

        logger.info("get_irradiance success")
Beispiel #3
0
    def _setup_array(self) -> None:
        """
        Setup the solar panel array as a Point per panel
        """
        self.turb_pos = []
        theta = np.radians(self.grid_angle)

        # where the turbines are
        self.turb_pos, self.site = create_turbines_in_grid(
            self.turbine_dx, self.turbine_dy, theta,
            FlickerMismatchGrid.n_turbines_per_side)

        # find the center grid which is symmetrical to all inner grids
        center_grid_coordinates = [(self.turb_pos[t][0], self.turb_pos[t][1])
                                   for t in (5, 6, 10, 9)]
        self.center_grid = Polygon(center_grid_coordinates)
        self.site_points, self.heat_map_template = self._setup_heatmap_template(
            self.center_grid.bounds)

        # where solar strings are
        string_width = module_width
        string_height = self.center_grid.bounds[3] - self.center_grid.bounds[1]
        y_pos = self.center_grid.bounds[1]
        biggest_string_coordinates = ((0, y_pos), (string_width, y_pos),
                                      (string_width, y_pos + string_height),
                                      (0, y_pos + string_height))
        biggest_string = Polygon(biggest_string_coordinates)

        self.array = []
        x_pos = self.center_grid.bounds[0]
        while x_pos < self.center_grid.bounds[2]:
            string = translate(biggest_string, x_pos, 0)
            string = string.intersection(self.center_grid)
            if string.area > 0:
                self.array.append(string)
            x_pos += string_width
        logger.info("setup_turbines_and_arrays success")

        # Create points centered on each module
        self.array_string_points = []
        for array in self.array:
            array_points = self.site_points.intersection(
                array.buffer(module_height / 4))

            if array_points.is_empty:
                continue

            string_points = self._setup_string_points(array_points)

            self.array_string_points.append(string_points)
        logger.info("setup_point_maps success")
Beispiel #4
0
    def run_parallel(self,
                     n_procs: int,
                     weight_option: tuple,
                     intervals: Optional[Sequence[range]] = None):
        """
        Runs create_heat_maps_irradiance in parallel

        :param n_procs:
        :param 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
        :param intervals: list of ranges to simulate; if none, simulate entire weather file's records
        :return: heat_map_shadow, heat_map_flicker
        """
        logger.info("run_parallel with {} processes".format(n_procs))
        pool = self._create_pool(n_procs)
        if intervals is None:
            intervals = self.step_intervals

        if 'power' in weight_option or 'poa' in weight_option:
            self._setup_irradiance()

        results = pool.imap(
            functools.partial(self.create_heat_maps,
                              weight_option=weight_option), intervals)

        # aggregate results and renormalize
        heat_maps_to_return = [
            copy.deepcopy(self.heat_map_template[0]) for _ in weight_option
        ]

        if 'power' in weight_option:
            subhourly_poa = np.repeat(self.poa, FlickerMismatch.steps_per_hour)
            total_poa = sum([sum(subhourly_poa[i]) for i in intervals])
        total_steps = sum([len(i) for i in intervals])
        for r, i in zip(results, intervals):
            for j, hm in enumerate(heat_maps_to_return):
                if weight_option[j] == 'poa':
                    hm += r[j] * sum(self.poa[i]) / total_poa
                elif weight_option[j] == 'power' or weight_option[j] == 'time':
                    hm += r[j] * len(i) / total_steps

        logger.info("Create_heat_map success")

        return tuple(heat_maps_to_return)
Beispiel #5
0
    def __init__(self,
                 lat: float,
                 lon: float,
                 turbine_nx: float,
                 turbine_ny: float,
                 angle: float = 0,
                 blade_length: int = 35,
                 angles_per_step: int = 1):
        """

        :param lat: latitude
        :param lon: longitude
        :param turbine_nx: number of turbine diameters for horizontal spacing in grid
        :param turbine_ny: number of turbine diameters for vertical spacing in grid
        :param angle: degree of rotation for turbine grid
        :param blade_length: meters
        :param angles_per_step: number of blade angles per step of the hour
        """
        FlickerMismatch.periodic = True
        self.center_grid = None
        self.turbine_dx = turbine_nx * blade_length * 2
        self.turbine_dy = turbine_ny * blade_length * 2
        self.grid_angle = int(angle) % 90
        self.n_rows_modules = int(turbine_nx / (0.124 * 12))
        super().__init__(lat,
                         lon,
                         blade_length=blade_length,
                         angles_per_step=angles_per_step)

        self.filename_full = "{}_{}_{}_{}_{}_{}_{}".format(
            self.lat, self.lon, self.steps_per_hour, self.angles_per_step,
            self.turbine_dx, self.turbine_dy, self.grid_angle)
        self.grid_turbine_shadow_file = Path(__file__).parent / "data" / str(
            self.filename_full + "_shd.pkl")
        logger.info(
            "Creating FlickerMismatchModel with filename_full {}".format(
                self.filename_full))
Beispiel #6
0
    def create_heat_maps(
        self,
        steps: range,
        weight_option: tuple,
    ) -> tuple:
        """
        Create shadow and flicker heat maps for a given range of simulation steps

        :param 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
        :param steps: which steps to run, must be within range calculated by steps_per_hour x angles_per_step
        :return: shadow heat map, flicker heat map
        """
        proc_id = mp.current_process().name
        logger.info("Proc {}: Starting heat maps {}".format(proc_id, steps))

        step_to_minute = 60 / self.steps_per_hour
        self.azi_ang, self.elv_ang, _ = get_sun_pos(self.lat,
                                                    self.lon,
                                                    step_to_minute,
                                                    steps=steps)

        self.turbine_shadow = get_turbine_shadows_timeseries(
            self.blade_length, steps, self.angles_per_step, self.azi_ang,
            self.elv_ang, self.wind_dir, FlickerMismatch.turbine_tower_shadow)

        by_poa = by_power = by_time = False

        for i in weight_option:
            if i == "poa":
                by_poa = True
                heat_map_shadow = copy.deepcopy(self.heat_map_template[0])
            elif i == "power":
                by_power = True
                heat_map_flicker = copy.deepcopy(self.heat_map_template[0])
            elif i == "time":
                by_time = True
                heat_map_time = copy.deepcopy(self.heat_map_template[0])
            else:
                raise ValueError("Unrecognized 'weight_option'")

        if not (by_poa or by_power or by_time):
            raise ValueError(
                "No valid 'weight_option' provided. Provide a list of selected ways to weight the shading "
                "from the set ('poa', 'power', 'time')")

        if by_poa or by_power:
            if not isinstance(self.poa, Sequence):
                self._setup_irradiance()
            total_poa = sum(self.poa[steps])

        progress_size = int(len(steps) / min(10, len(steps)))
        for i, step in enumerate(steps):
            if i % progress_size == 0:
                logger.info(
                    "Proc {} created heat maps for {} / 100 steps".format(
                        proc_id, int(i / len(steps) * 100)))

            hr = int(step / FlickerMismatch.steps_per_hour)

            shadows = self._calculate_turbine_shadow(i)

            if not shadows:
                continue

            if by_poa:
                poa_weight = self.poa[hr] / total_poa
                FlickerMismatch._calculate_shading(poa_weight, shadows,
                                                   self.site_points,
                                                   heat_map_shadow,
                                                   self.gridcell_width,
                                                   self.gridcell_height)

            if by_power:
                FlickerMismatch._calculate_power_loss(self.poa[hr],
                                                      self.elv_ang[i], shadows,
                                                      self.array_string_points,
                                                      heat_map_flicker,
                                                      self.gridcell_width,
                                                      self.gridcell_height)

            if by_time:
                FlickerMismatch._calculate_shading(1,
                                                   shadows,
                                                   self.site_points,
                                                   heat_map_time,
                                                   self.gridcell_width,
                                                   self.gridcell_height,
                                                   normalize_by_area=True)

        # normalize by angles per hour (since each will use the same weight) or by number of hours total
        step_normalize = self.angles_per_step if self.angles_per_step else 1
        if by_poa:
            heat_map_shadow /= step_normalize
        if by_power:
            heat_map_flicker /= step_normalize * len(steps)
        if by_time:
            heat_map_time /= step_normalize * len(steps)

        heat_maps_to_return = []
        for i in weight_option:
            if i == 'poa':
                heat_maps_to_return.append(heat_map_shadow)
            elif i == 'power':
                heat_maps_to_return.append(heat_map_flicker)
            elif i == 'time':
                heat_maps_to_return.append(heat_map_time)

        logger.info("Finished heat maps")
        return tuple(heat_maps_to_return)