Source code for oemof.thermal.concentrating_solar_power

# -*- coding: utf-8

"""
This module is designed to hold functions which are necessary for the CSP.

This file is part of project oemof (github.com/oemof/oemof-thermal). It's copyrighted
by the contributors recorded in the version control history of the file,
available from its original location:
oemof-thermal/src/oemof/thermal/concentrating_solar_power.py

SPDX-License-Identifier: MIT
"""


import pvlib
import pandas as pd
import numpy as np
import warnings


[docs]def csp_precalc(lat, long, collector_tilt, collector_azimuth, cleanliness, eta_0, c_1, c_2, temp_collector_inlet, temp_collector_outlet, temp_amb, a_1, a_2, a_3=0, a_4=0, a_5=0, a_6=0, loss_method='Janotte', irradiance_method='horizontal', **kwargs): r""" Calculates collectors efficiency and irradiance according to [1] and the heat of the thermal collector. For the calculation of irradiance pvlib [2] is used. .. csp_precalc_equation: :math:`Q_{coll} = E_{coll} \cdot \eta_C` functions used * pvlib.solarposition.get_solarposition * pvlib.tracking.singleaxis * calc_irradiance * calc_collector_irradiance * calc_iam * calc_eta_c * calc_heat_coll Parameters ---------- lat: numeric Latitude of the location. long: numeric Longitude of the location. collector_tilt: numeric The tilt of the collector. collector_azimuth: numeric The azimuth of the collector. Azimuth according to pvlib in decimal degrees East of North. cleanliness: numeric Cleanliness of the collector (between 0 and 1). a_1, a_2, a_3, a_4, a_5, a_6: numeric Parameters for the incident angle modifier. For loss method 'Janotte' a_1 and a_2 are required, for 'Andasol' a_1 to a_6 are required. eta_0: numeric Optical efficiency of the collector. c_1: numeric Thermal loss parameter 1. Required for both loss methods. c_2: numeric Thermal loss parameter 2. Required for loss method 'Janotte'. If loss method 'Andasol' is used, set it to 0. temp_collector_inlet: numeric or series with length periods Collectors inlet temperature. temp_collector_outlet: numeric or series with length periods Collectors outlet temperature. temp_amb: time indexed series Ambient temperature time series. loss_method: string, default 'Janotte' Valid values are: 'Janotte' or 'Andasol'. Describes, how the thermal losses and the incidence angle modifier are calculated. irradiance_method: string, default 'horizontal' Valid values are: 'horizontal' or 'normal'. Describes, if the horizontal direct irradiance or the direct normal irradiance is given and used for calculation. E_dir_hor/dni (depending on irradiance_method): time indexed series Irradiance for calculation. Returns ------- data : pandas.DataFrame Dataframe containing the following columns * collector_irradiance * eta_c * collector_heat collector_irradiance is the irradiance which reaches the collector after all losses (incl. cleanliness). **Comment** Series for ambient temperature and irradiance must have the same length and the same time index. Be aware of the time one. **Proposal of values** If you have no idea, which values your collector have, here are values, which were measured in [1] for a collector: a1: -0.00159, a2: 0.0000977, eta_0: 0.816, c1: 0.0622, c2: 0.00023. **Reference** [1] Janotte, N; et al: Dynamic performance evaluation of the HelioTrough \ collector demon-stration loop - towards a new benchmark in parabolic \ trough qualification, SolarPACES 2013 [2] William F. Holmgren, Clifford W. Hansen, and Mark A. Mikofski. “pvlib python: a python package for modeling solar energy systems.” Journal of Open Source Software, 3(29), 884, (2018). https://doi.org/10.21105/joss.00884 """ if loss_method not in ['Janotte', 'Andasol']: raise ValueError( "loss_method should be 'Janotte' or 'Andasol'") if irradiance_method not in ['normal', 'horizontal']: raise ValueError( "irradiance_method should be 'normal' or 'horizontal'") required_dict = {'horizontal': 'E_dir_hor', 'normal': 'dni'} irradiance_required = required_dict[irradiance_method] if irradiance_required not in kwargs: raise AttributeError( f"'{irradiance_required}' necessary for {irradiance_method} is not provided") if loss_method == 'Andasol' and (c_2 != 0): warnings.warn( "Parameter c_2 is not used for loss method 'Andasol'") if loss_method == 'Andasol' and (a_3 == 0 or a_4 == 0 or a_5 == 0 or a_6 == 0): warnings.warn( "Parameters a_3 to a_6 are required for loss method 'Andasol'") irradiance = kwargs.get(irradiance_required) if not temp_amb.index.equals(irradiance.index): raise IndexError(f"Index of temp_amb and {irradiance_required} have to be the same.") # Creation of a df with 2 columns data = pd.DataFrame({'irradiance': irradiance, 't_amb': temp_amb}) # Calculation of geometrical position of collector with the pvlib solarposition = pvlib.solarposition.get_solarposition( time=data.index, latitude=lat, longitude=long) # Calculation of the tracking data with the pvlib tracking_data = pvlib.tracking.singleaxis( solarposition['apparent_zenith'], solarposition['azimuth'], axis_tilt=collector_tilt, axis_azimuth=collector_azimuth) # Calculation of the irradiance which hits the collectors surface irradiance_on_collector = calc_irradiance( tracking_data['surface_tilt'], tracking_data['surface_azimuth'], solarposition['apparent_zenith'], solarposition['azimuth'], data['irradiance'], irradiance_method) # Calculation of the irradiance which reaches the collector after all # losses (cleanliness) collector_irradiance = calc_collector_irradiance( irradiance_on_collector, cleanliness) # Calculation of the incidence angle modifier iam = calc_iam( a_1, a_2, a_3, a_4, a_5, a_6, tracking_data['aoi'], loss_method) # Calculation of the collectors efficiency eta_c = calc_eta_c( eta_0, c_1, c_2, iam, temp_collector_inlet, temp_collector_outlet, data['t_amb'], collector_irradiance, loss_method) # Calculation of the collectors heat collector_heat = calc_heat_coll( eta_c, collector_irradiance) # Writing the results in the output df data['collector_irradiance'] = collector_irradiance data['eta_c'] = eta_c data['collector_heat'] = collector_heat return data
[docs]def calc_irradiance(surface_tilt, surface_azimuth, apparent_zenith, azimuth, irradiance, irradiance_method): r""" Parameters ---------- surface_tilt: series of numeric Panel tilt from horizontal. surface_azimuth: series of numeric Panel azimuth from north. apparent_zenith: series of numeric Solar zenith angle. azimuth: series of numeric Solar azimuth angle. irradiance: series of numeric Solar irraciance (dni or E_direct_horizontal). irradiance_method: str Describes, if the horizontal direct irradiance or the direct normal irradiance is given and used for calculation. Returns ------- irradiance_on_collector: series of numeric Irradiance which hits collectors surface. """ if irradiance_method == 'horizontal': poa_horizontal_ratio = pvlib.irradiance.poa_horizontal_ratio( surface_tilt, surface_azimuth, apparent_zenith, azimuth) poa_horizontal_ratio[poa_horizontal_ratio < 0] = 0 irradiance_on_collector = irradiance * poa_horizontal_ratio elif irradiance_method == 'normal': irradiance_on_collector = pvlib.irradiance.beam_component( surface_tilt, surface_azimuth, apparent_zenith, azimuth, irradiance) return irradiance_on_collector
[docs]def calc_collector_irradiance(irradiance_on_collector, cleanliness): r""" Subtracts the losses of dirtiness from the irradiance on the collector .. calc_collector_irradiance_equation: :math:`E_{coll} = E^*_{coll} \cdot X^{3/2}` Parameters ---------- irradiance_on_collector: series of numeric Irradiance which hits collectors surface. x: numeric Cleanliness of the collector (between 0 and 1). Returns ------- collector_irradiance: series of numeric Irradiance on collector after all losses. """ collector_irradiance = irradiance_on_collector * cleanliness**1.5 collector_irradiance[collector_irradiance < 0] = 0 collector_irradiance = collector_irradiance.fillna(0) return collector_irradiance
[docs]def calc_iam(a_1, a_2, a_3, a_4, a_5, a_6, aoi, loss_method): r""" Calculates the incidence angle modifier depending on the loss method .. calc_iam_equation: method 'Janotte': :math:`\kappa(\varTheta) = 1 - a_1 \cdot \vert\varTheta\vert- a_2 \cdot \vert\varTheta\vert^2` method 'Andasol': :math:`\kappa(\varTheta) = 1 - a_1 \cdot \vert\varTheta\vert - a_2 \cdot \vert\varTheta\vert^2- a_3 \cdot \vert\varTheta\vert^3 - a_4 \cdot \vert\varTheta\vert^4 - a_5 \cdot \vert\varTheta\vert^5 - a_6 \cdot \vert\varTheta\vert^6` Parameters ---------- a_1, a_2, a_3, a_4, a_5, a_6: numeric Parameters for the incident angle modifier. For loss method 'Janotte' a_1 and a_2 are required, for 'Andasol' a_1 to a_6 are required. aoi: series of numeric Angle of incidence. loss_method: string, default 'Janotte' Valid values are: 'Janotte' or 'Andasol'. Describes, how the thermal losses and the incidence angle modifier are calculated. Returns ------- Incidence angle modifier: series of numeric """ if loss_method == 'Janotte': iam = 1 - a_1 * abs(aoi) - a_2 * aoi**2 if loss_method == 'Andasol': iam = (1 - a_1 * abs(aoi) - a_2 * aoi**2 - a_3 * aoi**3 - a_4 * aoi**4 - a_5 * aoi**5 - a_6 * aoi**6) return iam
[docs]def calc_eta_c(eta_0, c_1, c_2, iam, temp_collector_inlet, temp_collector_outlet, temp_amb, collector_irradiance, loss_method): r""" Calculates collectors efficiency depending on the loss method .. calc_eta_c_equation: method 'Janotte': :math:`\eta_C = \eta_0 \cdot \kappa(\varTheta) - c_1 \cdot \frac{\Delta T}{E_{coll}} - c_2 \cdot \frac{{\Delta T}^2}{E_{coll}}` method 'Andasol': :math:`\eta_C = \eta_0 \cdot \kappa(\varTheta) - \frac{c_1}{E_{coll}}` Parameters ---------- eta_0: numeric Optical efficiency of the collector. c_1: numeric Thermal loss parameter 1. Required for both loss methods. c_2: numeric Thermal loss parameter 2. Required for loss method 'Janotte'. iam: series of numeric Incidence angle modifier. temp_collector_inlet: numeric, in °C Collectors inlet temperature. temp_collector_outlet: numeric, in °C Collectors outlet temperature. temp_amb: series of numeric, in °C Ambient temperature. collector_irradiance: series of numeric Irradiance on collector after all losses. loss_method: string, default 'Janotte' Valid values are: 'Janotte' or 'Andasol'. Describes, how the thermal losses and the incidence angle modifier are calculated. Returns ------- collectors efficiency: series of numeric """ if loss_method == 'Janotte': delta_temp = (temp_collector_inlet + temp_collector_outlet) / 2 - temp_amb eta_c = eta_0 * iam - c_1 * delta_temp / collector_irradiance - c_2\ * delta_temp ** 2 / collector_irradiance if loss_method == 'Andasol': eta_c = eta_0 * iam - c_1 / collector_irradiance eta_c[eta_c < 0] = 0 eta_c[eta_c == np.inf] = 0 eta_c = eta_c.fillna(0) return eta_c
[docs]def calc_heat_coll(eta_c, collector_irradiance): r""" .. csp_heat_equation: :math:`\dot Q_{coll} = E_{coll} \cdot \eta_C` Parameters ---------- eta_c: series of numeric collectors efficiency. collector_irradiance: series of numeric Irradiance on collector after all losses. Returns ------- collectors heat: series of numeric """ collector_heat = collector_irradiance * eta_c return collector_heat