solargainspvlib

Overview

Perform calculations of solar gains for buildings using irradiance models.

This class provides methods to process input data and calculate solar gains by considering weather conditions, window configurations, and solar irradiance models. It integrates with pvlib to compute solar positions and irradiance values. The class supports different irradiance models, such as “isotropic” and “haydavies”, and handles missing input gracefully.

Key facts

  • Method key: SolarGainsPVLib

Requirements

Required keys (specify in objects)

  • id

  • latitude[degree]

  • longitude[degree]

Optional keys (specify in objects)

  • None

Required data (specify in data)

  • weather

  • windows

Optional data (specify in data)

  • None

Outputs

Summary metrics

  • None

Timeseries columns

  • None

Public methods

  • get_input_data

    def get_input_data(self, obj, data):
      object_id = obj[O.ID]
      windows = data.get(O.WINDOWS, None)
      if windows is not None:
          windows = windows.loc[windows[O.ID] == object_id]
          windows = windows if not windows.empty else None
      input_data = {
          "latitude": obj[O.LAT],
          "longitude": obj[O.LON],
          "weather": data[O.WEATHER],
          "windows": windows,
      }
      return input_data
    
  • run

    def run(self, weather, windows, latitude, longitude):
      """Calculate solar gains for a building.
    
      Args:
          weather (pd.DataFrame): Weather data.
          windows (pd.DataFrame): Windows data.
          latitude (float): Latitude.
          longitude (float): Longitude.
          model (str, optional): Irradiance model to use. Default is "isotropic".
    
      Returns:
          pd.DataFrame: Solar gains for each timestep.
    
      Raises:
          ValueError: If the irradiance model is unknown.
    
      Caching rules:
      - Cache solpos and poa_global.
      - Do NOT cache final total solar gains.
      - Weather identity for caches must depend on location and average GHI in addition to time grid.
      - Window fingerprint for POA cache uses only tilt and orientation.
      """
      if windows is None:
          return pd.DataFrame({O.GAINS_SOLAR: np.zeros(len(weather), dtype=np.float32)}, index=weather.index)
    
      # Weather/location signatures for caching
      timezone_info = weather.index[0].tzinfo
      tz_offset = 0 if timezone_info is None else timezone_info.utcoffset(None).total_seconds() / 3600
      lat_r, lon_r = _round_loc(latitude, longitude)
      wsig = _weather_signature_with_ghi(weather.index, weather[C.SOLAR_GHI])
    
      # Cache solar position (solpos)
      sp_key = (wsig, lat_r, lon_r, tz_offset)
      solpos = _SOLPOS_CACHE.get(sp_key)
      if solpos is None:
          location = pvlib.location.Location(latitude, longitude, tz=tz_offset)
          solpos = location.get_solarposition(pd.to_datetime(weather.index, utc=True), method="nrel_numba")
          _SOLPOS_CACHE[sp_key] = solpos
    
      total_solar_gains = np.zeros(len(weather), dtype=np.float32)
      zenith = solpos["zenith"]
      azimuth = solpos["azimuth"]
      ghi = weather[C.SOLAR_GHI]
      dhi = weather[C.SOLAR_DHI]
      dni = weather[C.SOLAR_DNI]
    
      # Loop windows; cache POA per (weather/location/tilt/azimuth)
      for _, window in windows.iterrows():
          tilt = float(window[C.TILT])
          orientation = float(window[C.ORIENTATION])
          poa_key = (wsig, lat_r, lon_r, tz_offset, round(tilt, 3), round(orientation, 3))
          poa = _POA_CACHE.get(poa_key)
          if poa is None:
              irr = pvlib.irradiance.get_total_irradiance(
                  surface_tilt=tilt,
                  surface_azimuth=orientation,
                  solar_zenith=zenith,
                  solar_azimuth=azimuth,
                  dni=dni,
                  ghi=ghi,
                  dhi=dhi,
                  dni_extra=None,
                  model="isotropic",
              )
              poa = irr["poa_global"].to_numpy(dtype=np.float32, copy=False)
              _POA_CACHE[poa_key] = poa
    
          # Compute window gains from POA. Cast scalars to np.float32 so
          # `poa * area * g * sh` stays float32 (Python `float` would promote
          # the intermediate array to float64 and force a copy back).
          area = np.float32(window[C.AREA])
          g = np.float32(window[C.G_VALUE])
          sh = np.float32(window[C.SHADING])
          total_solar_gains += poa * area * g * sh
    
      return pd.DataFrame({O.GAINS_SOLAR: total_solar_gains}, index=weather.index)