Example #1
0
def test_solar(solar_resource):
    data = solar_resource.data
    for key in ('df', 'dn', 'wspd', 'tdry', 'year', 'month', 'day', 'hour',
                'minute', 'tz'):
        assert (key in data)
    model = pv.default("PVWattsNone")
    model.SolarResource.solar_resource_file = solar_resource.filename
    model.execute(0)
    assert (model.Outputs.annual_energy == approx(5743.338))
    model = pv.default("PVWattsNone")
    model.SolarResource.solar_resource_data = solar_resource.data
    model.execute(1)
    assert (model.Outputs.annual_energy == approx(5743.537))
Example #2
0
    def __init__(self,
                 site: SiteInfo,
                 system_capacity_kw: float,
                 detailed_not_simple: bool = False):
        """

        :param system_capacity_kw:
        :param detailed_not_simple:
            Detailed model uses Pvsamv1, simple uses PVWatts
        """
        self.detailed_not_simple: bool = detailed_not_simple

        if not detailed_not_simple:
            system_model = Pvwatts.default("PVWattsSingleOwner")
            financial_model = Singleowner.from_existing(
                system_model, "PVWattsSingleOwner")
        else:
            system_model = Pvsam.default("FlatPlatePVSingleOwner")
            financial_model = Singleowner.from_existing(
                system_model, "FlatPlatePVSingleOwner")

        super().__init__("SolarPlant", site, system_model, financial_model)

        self.system_model.SolarResource.solar_resource_data = self.site.solar_resource.data

        self.system_capacity_kw: float = system_capacity_kw
Example #3
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")
Example #4
0
 def default():
     """Get the default PySAM pvwattsv7 object"""
     res_file = os.path.join(
         DEFAULTSDIR, 'USA AZ Phoenix Sky Harbor Intl Ap (TMY3).csv')
     obj = PySamPV7.default('PVWattsNone')
     obj.SolarResource.solar_resource_file = res_file
     obj.execute()
     return obj
Example #5
0
def calculate_power(solar_data, pv_dict):
    """Use PVWatts to translate weather data into power.

    :param dict solar_data: weather data as returned by :meth:`Psm3Data.to_dict`.
    :param dict pv_dict: solar plant attributes.
    :return: (*numpy.array*) hourly power output.
    """
    pv_dat = pssc.dict_to_ssc_table(pv_dict, "pvwattsv7")
    pv = PVWatts.wrap(pv_dat)
    pv.SolarResource.assign({"solar_resource_data": solar_data})
    pv.execute()
    return np.array(pv.Outputs.gen)
Example #6
0
def test_solar():
    solar = str(Path(__file__).parent / "blythe_ca_33.617773_-114.588261_psmv3_60_tmy.csv")
    data = tools.TMY_CSV_to_solar_data(solar)
    assert (data['lat'] == 33.61)
    assert (data['lon'] == -114.58)
    assert (data['dn'][7] == 262)
    assert (data['df'][7] == 16)
    assert (data['gh'][7] == 27)
    assert (data['tdry'][7] == pytest.approx(8.96, 0.1))
    model = pv.default("PVwattsNone")
    model.SolarResource.solar_resource_data = data
    model.execute()
    aep = model.Outputs.annual_energy
    model.SolarResource.solar_resource_file = solar
    model.execute()
    assert(aep == pytest.approx(model.Outputs.annual_energy, 1))
Example #7
0
def calculate_max_hybrid_aep(site_info: SiteInfo,
                             num_turbines: int,
                             solar_capacity_kw: float
                             ) -> dict:
    """
    Calculates the max total pv and solar annual energy output by assuming no wake, gcr or flicker losses.
    All other factors and losses are not adjusted because they remain constant throughout the optimization
    :return: dictionary of "wind", "solar" and "total" max AEPs
    """
    upper_bounds = dict()
    
    # wind
    wind_model = windpower.default("WindPowerSingleOwner")
    wind_model.Resource.wind_resource_data = site_info.wind_resource.data
    
    wind_params_orig = wind_model.export()
    wind_model.Farm.wind_farm_xCoordinates = np.zeros(num_turbines)
    wind_model.Farm.wind_farm_yCoordinates = np.zeros(num_turbines)
    wind_model.Farm.system_capacity = num_turbines * max(wind_model.Turbine.wind_turbine_powercurve_powerout)
    wind_model.Farm.wind_farm_wake_model = 3  # constant wake loss model which we can set to 0
    wind_model.Losses.wake_int_loss = 0
    wind_model.execute(0)
    
    # solar
    solar_model = pvwatts.default("PVWattsSingleOwner")
    solar_model.SolarResource.solar_resource_data = site_info.solar_resource.data
    solar_model.SystemDesign.array_type = 2  # single-axis tracking
    
    solar_params_orig = solar_model.export()
    solar_model.SystemDesign.gcr = 0.01  # lowest possible gcr
    solar_model.SystemDesign.system_capacity = solar_capacity_kw
    solar_model.execute(0)
    
    upper_bounds['wind'] = wind_model.Outputs.annual_energy / 1000
    upper_bounds['solar'] = solar_model.Outputs.annual_energy / 1000
    upper_bounds['total'] = upper_bounds['wind'] + upper_bounds['solar']
    
    # restore original parameters
    wind_model.assign(wind_params_orig)
    solar_model.assign(solar_params_orig)
    
    return upper_bounds
    def get_solar_energy_output(self):
        ssc = pssc.PySSC()
        f = open(self.sam_export_json)
        self.dic = json.load(f)
        self.dic['solar_resource_file'] = self.fp_srw

        ### uncomment if you want to change any model input parameters
        #         self.dic['system_capacity'] = 20000
        #         self.dic['module_type'] = 0
        #         self.dic['dc_ac_ratio'] = 1.3
        #         self.dic['array_type'] =  2
        #         self.dic['tilt'] =  35
        #         self.dic['azimuth'] = 180
        #         self.dic['gcr'] = 0.40
        #         self.dic['losses'] = 14
        #         self.dic['en_snowloss'] =  0
        #         self.dic['inv_eff'] = 95

        pv_dat = pssc.dict_to_ssc_table(self.dic, "pvwattsv7")
        grid_dat = pssc.dict_to_ssc_table(self.dic, "grid")
        f.close()
        pv = PVWatts.wrap(pv_dat)
        grid = Grid.from_existing(pv)
        grid.assign(Grid.wrap(grid_dat).export())
        pv.execute()
        grid.execute()
        self.json_dict = pv.Outputs.export()
        #         print(self.json_dict.keys())
        self.df_output = pd.DataFrame()
        self.df_output[self.year] = self.json_dict['gen']
        for col in self.df_output.columns:
            self.df_output[col] = preprocessing.minmax_scale(
                self.df_output[col].values.reshape(1, -1),
                feature_range=(0, 1),
                axis=1,
                copy=True).T

        if self.df_all is None:
            self.df_all = self.df_output.copy()
        else:
            self.df_all = pd.concat([self.df_all, self.df_output], axis=1)
Example #9
0
def run_solar(solar_csv, latitude):
    s = pv.default("PVWattsNone")

    ##### Parameters #######
    s.SolarResource.solar_resource_file = solar_csv
    s.SystemDesign.array_type = 0
    s.SystemDesign.azimuth = 180
    s.SystemDesign.tilt = abs(latitude)
    nameplate_capacity = 1000  #kw
    s.SystemDesign.system_capacity = nameplate_capacity  # System Capacity (kW)
    s.SystemDesign.dc_ac_ratio = 1.1  #DC to AC ratio
    s.SystemDesign.inv_eff = 96  #default inverter eff @ rated power (%)
    s.SystemDesign.losses = 14  #other DC losses (%) (14% is default from documentation)
    ########################

    s.execute()
    output_cf = np.array(
        s.Outputs.ac) / (nameplate_capacity * 1000
                         )  #convert AC generation (w) to capacity factor

    return output_cf
Example #10
0
def test_reopt_sizing_pvwatts(solar_resource):
    round = 0

    tracker = SummaryTracker()
    while round < 25:  # multiple runs required to check for memory leaks
        round += 1

        sys = pv.default("PVWattsBatteryCommercial")
        sys.SolarResource.solar_resource_file = solar_resource
        batt = bt.from_existing(sys, "PVWattsBatteryCommercial")
        sys.SolarResource.solar_resource_data = dict({'lat': 3, 'lon': 3})
        batt.Battery.crit_load = [0] * 8760
        fin = ur.from_existing(sys, "PVWattsBatteryCommercial")

        post = sys.Reopt_size_battery_post()

    assert ('Scenario' in post['reopt_post'])
    assert (
        post['reopt_post']['Scenario']['Site']['latitude'] == pytest.approx(
            3, 0.1))
    tracker_diff = tracker.diff()
    tracker.print_diff()
    def getSolarCF(solarResourceData):
        s = pv.default("PVWattsNone")

        ##### Parameters #######
        s.SolarResource.solar_resource_data=solarResourceData
        s.SystemDesign.array_type = 0
        s.SystemDesign.azimuth = 180
        s.SystemDesign.tilt = abs(solarResourceData['lat'])
        nameplate_capacity = 1000 #kw
        s.SystemDesign.system_capacity = nameplate_capacity   # System Capacity (kW)
        s.SystemDesign.dc_ac_ratio = 1.1 #DC to AC ratio
        s.SystemDesign.inv_eff = 96 #default inverter eff @ rated power (%)
        s.SystemDesign.losses = 14 #other DC losses (%) (14% is default from documentation)
        ########################

        s.execute()
        solarCF = np.array(s.Outputs.ac) / (nameplate_capacity * 1000) #convert AC generation (w) to capacity factor

        if args.verbose:
            print('\t','Average Solar CF = {cf}'.format(cf=round(np.average(solarCF),2)))

        return solarCF
Example #12
0
def retrieve_data(solar_plant, email, api_key, year="2016", rate_limit=0.5):
    """Retrieves irradiance data from NSRDB and calculate the power output using
    the System Adviser Model (SAM).

    :param pandas.DataFrame solar_plant: plant data frame.
    :param str email: email used to`sign up <https://developer.nrel.gov/signup/>`_.
    :param str api_key: API key.
    :param str year: year.
    :param int/float rate_limit: minimum seconds to wait between requests to NREL
    :return: (*pandas.DataFrame*) -- data frame with *'Pout'*, *'plant_id'*,
        *'ts'* and *'ts_id'* as columns. Values are power output for a 1MW generator.
    """

    # SAM only takes 365 days.
    try:
        leap_day = (pd.Timestamp("%s-02-29-00" % year).dayofyear - 1) * 24
        is_leap_year = True
        dates = pd.date_range(start="%s-01-01-00" % 2015,
                              freq="H",
                              periods=365 * 24)
        dates = dates.map(lambda t: t.replace(year=int(year)))
    except ValueError:
        leap_day = None
        is_leap_year = False
        dates = pd.date_range(start="%s-01-01-00" % year,
                              freq="H",
                              periods=365 * 24)

    # Identify unique location
    coord = get_plant_id_unique_location(solar_plant)

    data = pd.DataFrame({"Pout": [], "plant_id": [], "ts": [], "ts_id": []})

    # PV tracking ratios
    # By state and by interconnect when EIA data do not have any solar PV in
    # the state
    pv_info = get_pv_tracking_data()
    zone_id = solar_plant.zone_id.unique()
    frac = {}
    for i in zone_id:
        state = id2abv[i]
        frac[i] = get_pv_tracking_ratio_state(pv_info, [state])
        if frac[i] is None:
            frac[i] = get_pv_tracking_ratio_state(
                pv_info, list(interconnect2abv[abv2interconnect[state]]))

    # Inverter Loading Ratio
    ilr = 1.25
    api = NrelApi(email, api_key, rate_limit)

    for key in tqdm(coord.keys(), total=len(coord)):
        lat, lon = key[1], key[0]
        solar_data = api.get_psm3_at(
            lat,
            lon,
            attributes="dhi,dni,wind_speed,air_temperature",
            year=year,
            leap_day=False,
            dates=dates,
        ).to_dict()

        for i in coord[key]:
            data_site = pd.DataFrame({
                "ts":
                pd.date_range(start="%s-01-01-00" % year,
                              end="%s-12-31-23" % year,
                              freq="H")
            })
            data_site["ts_id"] = range(1, len(data_site) + 1)
            data_site["plant_id"] = i

            power = 0
            for j, axis in enumerate([0, 2, 4]):
                pv_dict = {
                    "system_capacity": ilr,
                    "dc_ac_ratio": ilr,
                    "tilt": 30,
                    "azimuth": 180,
                    "inv_eff": 94,
                    "losses": 14,
                    "array_type": axis,
                    "gcr": 0.4,
                    "adjust:constant": 0,
                }

                pv_dat = pssc.dict_to_ssc_table(pv_dict, "pvwattsv7")
                pv = PVWatts.wrap(pv_dat)
                pv.SolarResource.assign({"solar_resource_data": solar_data})
                pv.execute()

                ratio = frac[solar_plant.loc[i].zone_id][j]
                power += ratio * np.array(pv.Outputs.gen)

            if is_leap_year is True:
                data_site["Pout"] = np.insert(power, leap_day,
                                              power[leap_day - 24:leap_day])
            else:
                data_site["Pout"] = power

            data = data.append(data_site, ignore_index=True, sort=False)

    data["plant_id"] = data["plant_id"].astype(np.int32)
    data["ts_id"] = data["ts_id"].astype(np.int32)

    data.sort_values(by=["ts_id", "plant_id"], inplace=True)
    data.reset_index(inplace=True, drop=True)

    return data
    def _setup_simulation(self) -> None:
        """
        Wind simulation
            -> PySAM windpower model

        Solar simulation
            -> Surrogate model of PySAM Pvwatts model since the AEP scales linearly and independently
            w.r.t solar capacity and gcr
        """
        def run_wind_model(windmodel: windpower.Windpower):
            windmodel.Farm.system_capacity = \
                max(windmodel.Turbine.wind_turbine_powercurve_powerout) * len(windmodel.Farm.wind_farm_xCoordinates)
            windmodel.execute(0)
            return windmodel.Outputs.annual_energy

        def run_pv_model(pvmodel: pvwatts.Pvwattsv7):
            cap = pvmodel.SystemDesign.system_capacity
            gcr = pvmodel.SystemDesign.gcr
            est = cap * self._solar_size_aep_multiplier * self.solar_gcr_loss_multiplier(
                gcr)
            # pvmodel.execute()
            # rl = pvmodel.Outputs.annual_energy
            # err = (rl - est)/rl
            # if err > 0.05:
            #     print("High approx error found with {} kwh and {} gcr of {}".format(cap, gcr, err))
            return est

        # create wind model
        self._scenario = dict()
        wind_model = windpower.default("WindPowerSingleOwner")
        wind_model.Resource.wind_resource_data = self.site_info.wind_resource.data
        self.turb_diam = wind_model.Turbine.wind_turbine_rotor_diameter
        wind_model.Farm.wind_farm_wake_model = 2  # use eddy viscosity wake model

        self._scenario['Wind'] = (wind_model, run_wind_model)

        # create pv model
        solar_model = pvwatts.default("PVWattsSingleOwner")
        solar_model.SolarResource.solar_resource_data = self.site_info.solar_resource.data
        solar_model.SystemDesign.array_type = 2  # single-axis tracking
        solar_model.SystemDesign.tilt = 0

        # setup surrogate
        solar_model.execute(0)
        self._solar_size_aep_multiplier = solar_model.Outputs.annual_energy / solar_model.SystemDesign.system_capacity

        solar_model.SystemDesign.gcr = 0.01  # lowest possible gcr
        solar_model.SystemDesign.system_capacity = 1
        solar_model.execute(0)
        if solar_model.Outputs.annual_energy > 0:
            self._solar_gcr_loss_multiplier[
                'unit'] = solar_model.Outputs.annual_energy
        else:
            raise RuntimeError(
                "Solar GCR Loss Multiplier: Setup failed due to 0 for unit value"
            )

        self._scenario['Solar'] = (solar_model, run_pv_model)

        # estimate max AEP
        self.upper_bounds = calculate_max_hybrid_aep(self.site_info,
                                                     self.num_turbines,
                                                     self.solar_capacity_kw)

        logger.info(
            "Setup Wind and Solar models. Max AEP is {} for wind, {} solar, {} total"
            .format(self.upper_bounds['wind'], self.upper_bounds['solar'],
                    self.upper_bounds['total']))
# PVWatts simulation.

# The json file is generated from SAM using the "Generate Code" menu item
# in the added simulation case.  Choose "JSON for inputs" and a .json file
# with the title of your simulation case will be created where you select with
# the "Open" button on the file dialog.
json_file_path = 'Examples/100kW_PVWatts.json'  # Change this file name to yours!
with open(json_file_path) as f:
    dic = json.load(f)
# The next seven lines are needed to load the PySAM data structures with the
# inputs from the json file.
pv_dat = pssc.dict_to_ssc_table(dic, "pvwattsv7")
grid_dat = pssc.dict_to_ssc_table(dic, "grid")
ur_dat = pssc.dict_to_ssc_table(dic, "utilityrate5")
cl_dat = pssc.dict_to_ssc_table(dic, "cashloan")
pv = PVWatts.wrap(pv_dat)
grid = Grid.from_existing(pv)
ur = UtilityRate.from_existing(pv)
cl = Cashloan.from_existing(pv)
grid.assign(Grid.wrap(grid_dat).export())
ur.assign(UtilityRate.wrap(ur_dat).export())
cl.assign(Cashloan.wrap(cl_dat).export())

# The models are executed in order.  Note that the outputs from the first
# simulation are automatically available for the next one, and so on.  :-)
pv.execute()
grid.execute()
ur.execute()
cl.execute()

if verbose:  # Print out some results.  The variable names can be found in
Example #15
0
                ('Json file', '*.json'),
                ('All files', '*.*'),
            ])
    if verbose:
        print(json_file_path)

except NameError:
    print('NameError: with the json file')
else:
    with open(json_file_path) as f:
        dic = json.load(f)
pv_dat = pssc.dict_to_ssc_table(dic, "pvwattsv7")
grid_dat = pssc.dict_to_ssc_table(dic, "grid")
ur_dat = pssc.dict_to_ssc_table(dic, "utilityrate5")
cl_dat = pssc.dict_to_ssc_table(dic, "cashloan")
pv = PVWattsCommercial.wrap(pv_dat)
grid = Grid.from_existing(pv)
ur = UtilityRate.from_existing(pv, 'PVWattsCommercial')
cl = Cashloan.from_existing(pv, 'PVWattsCommercial')
grid.assign(Grid.wrap(grid_dat).export())
ur.assign(UtilityRate.wrap(ur_dat).export())
cl.assign(Cashloan.wrap(cl_dat).export())

degradation = cl.SystemOutput.degradation[0]
if verbose:
    print('degradation', degradation)
if testing:
    pv.execute()
    grid.execute()
    ur.execute()
    cl.execute()
Example #16
0
# See function documentation for full parameter list
nsrdbfetcher = tools.FetchResourceFiles(tech='solar',
                                        nrel_api_key=sam_api_key,
                                        nrel_api_email=sam_email)

# --- List of (lon, lat) tuples or Shapely points ---
lon_lats = [(lon, lat)]
nsrdbfetcher.fetch(lon_lats)

# --- Get resource data file path ---
nsrdb_path_dict = nsrdbfetcher.resource_file_paths_dict
nsrdb_fp = nsrdb_path_dict[lon_lats[0]]
if nsrdb_fp is not None:

    # --- Initialize Generator ---
    generator = pv.default('PVWattsSingleOwner')
    generator.SolarResource.assign({'solar_resource_file': nsrdb_fp})

    # --- Initialize Financial Model ---
    financial = so.from_existing(generator, 'PVWattsSingleOwner')

    # --- Execute Models ---
    print('PVWatts V7 - Single Owner Results')
    generator.execute()
    print('capacity factor = {:.3f}'.format(generator.Outputs.capacity_factor))
    financial.execute()
    print('npv = ${:,.2f}'.format(
        financial.Outputs.project_return_aftertax_npv))
else:
    print('Solar resource file does not exist. Skipping solar simulations.')
Example #17
0
    def __init__(self, tech,
                re_capacity_mw, #float, or tuple with lower and upper bounds
                batt_capacity_mw=0, #float, or tuple with lower and upper bounds
                batt_duration=[0,2,4], #0, 2, or 4hr
                verbose=True,
                params=None):
        
        if verbose:
            log.info('\n')
            log.info(f'Initializing BayesianSystemDesigner for {tech}')
        
        self.tech = tech

        if isinstance(re_capacity_mw, (float, int)):
            self.re_capacity_kw=re_capacity_mw * 1000  # pysam takes this as kw
        elif isinstance(re_capacity_mw, (tuple, list)):
            self.re_capacity_kw = (re_capacity_mw[0] * 1000, re_capacity_mw[1] * 1000)
        
        if isinstance(batt_capacity_mw, (float, int)):
            self.batt_capacity_kw = batt_capacity_mw * 1000  # pysam takes this as kw
        elif isinstance(batt_capacity_mw, (tuple, list)):
            self.batt_capacity_kw = (batt_capacity_mw[0] * 1000, batt_capacity_mw[1] * 1000)
        else:
            self.batt_capacity_kw = 0

        if isinstance(batt_duration, (float, int)):
            self.batt_duration = batt_duration
        elif isinstance(batt_duration, (tuple, list)):
            self.batt_duration = np.array(batt_duration)

        self.storage = False
        if isinstance(self.batt_capacity_kw, (int, float)):
            if self.batt_capacity_kw > 0:
                self.storage = True
        elif isinstance(self.batt_capacity_kw, (tuple)):
            if max(self.batt_capacity_kw) > 0:
                self.storage = True

        # --- Initiate Generator and assign params if not passed ---
        if tech == 'pv':
            self.gen = pv.default('PVWattsSingleOwner')
            self.default_params = self.gen.SystemDesign.export()

            if params == None:  # assign default grid of solar params
                self.param_grid = {
                    'SystemDesign':{
                        'system_capacity': self.re_capacity_kw,
                        'subarray1_track_mode': 1, #np.array([0, 1, 2, 4]), #1 = fixed
                        'subarray1_tilt': np.arange(0, 90, 10),
                        'subarray1_azimuth': np.arange(1, 359, 10),
                        'dc_ac_ratio': np.arange(0.8, 1.3, 0.1),
                    }
                }

        elif tech == 'wind':
            self.gen = wp.default('WindPowerSingleOwner')
            self.default_params = self.gen.Turbine.export()

            if params == None:  # assign default grid of wind params
                self.param_grid = {
                    'Turbine': {'wind_turbine_hub_ht': np.array([60, 170]), 'turbine_class': np.array([1, 10])},
                    'Farm': {'system_capacity': self.re_capacity_kw},
                }
        
        else: raise NotImplementedError(f'Please write a wrapper to account for the new technology type {tech}')


        # --- Add Battery Params ---
        if self.storage:
            self.param_grid['BatteryTools'] = {'desired_power': self.batt_capacity_kw,
                                               'desired_capacity': self.batt_duration,
                                               'desired_voltage':500}
            self.param_grid['BatterySystem'] = {'en_batt':1,
                                                'batt_meter_position':0}
        else:
            self.param_grid['BatterySystem'] = {'en_batt': 0}