Park Calculation Service

CalcParkService gives access to the PARK module. This service is more complex than the other calcualtion services.

It contains the two function, CalcParkService.GetParkCalc for getting a park objects and CalcParkService.SetParkCalc for passing it back to windPRO.

CalcParkService.GetTApiWtgId() gets objects that are needed to connects wind turbine generator objects with the PARK calculation (similar to factory.TApiWtgId()). The connection is done by adding the Handle of the WTG objects. A list of these TApiWtgId objects needs to be combined into factory.TApiObjectHandles() and added to CalcParkService.NewWtgs or CalcParkService.ExistWtgs.

There are multiple different PARK calculation types. You can change them with the property ParkCalcType. A PARK calulation in Scripting always has all properties for all calculations. For finding out what properties are needed to be changed you can setup your calculation in the GUI as it is intended and then see what properties have changed.

There is access to the time varying calculation where TI is used to dynamically set the wake decay constants. CalcParkService.GetWdcTiSetup and CalcParkService.SetWdcTiSetup for getting and setting those properties respectively.

Properties of the calculation object can be found here TApiCalcPark.

Find below an example how to use PARK calculations:

"""
Copyright 2024 EMD International
License for this script: MIT https://opensource.org/license/mit/
License for windPRO commercial software: https://www.emd-international.com/contact-us/general-terms-conditions-sale/
"""

import os
from datetime import datetime
from windproapi.utils import get_windpro_sample_path
from windproapi import nan_to_skipvalue
from windproapi import WindProApi

# Opening windPRO
_windproapi = WindProApi()

working_dir = os.path.join(get_windpro_sample_path('4.1'), 'New Salem', '4.1')
project_path = os.path.join(working_dir, 'New Salem.w41p')

_windproapi.start_windpro_random_port()

# Services
project_service = _windproapi.get_service('ProjectService')
objects_service = _windproapi.get_service('ObjectsService')
calculation_service = _windproapi.get_service('CalculationService')
calc_park_service = _windproapi.get_service('CalcParkService')
obj_wtg_service = _windproapi.get_service('ObjWtgService')
factory = _windproapi.get_factory('WindproService')

# Loading project
project_service.LoadFromFile(filename=project_path)

# New PARK calculation
handle_park = calculation_service.CreateEmpty(calcType='CalcPark')
calculation_service.OpenCalcForEdit(handle=handle_park)
park_calc = calc_park_service.GetParkCalc()

# ParkTypeSiteData is a WAsP resource calculation together with a PARK calculation.
park_calc.ParkCalcType = 'ParkTypeSiteData'
# PARK2 type of calculation
park_calc.WakeModelType = 'ParkWakeModelNOJensenPark2'
# Name the calculation with time stamp of the generation
park_calc.Name = 'New Wake Calculation {}'.format(datetime.today().strftime('%Y-%m-%d %H:%M:%S'))

# Adding site data object that is necessary for the WAsP calcualtion
site_data_objs = objects_service.GetObjects(apiObjType='SiteData')
obj_site_data = [o for o in site_data_objs if o.UserDescription == 'for WAsP'][0]
intArr = factory.TApiObjectHandles()
intArr['int'].append(obj_site_data.Handle)
park_calc.StatData.SiteDataObjects = intArr

# Making an array of WTGs that can be added to the park
wtgids = factory.TApiWtgIds()
for w in objects_service.GetObjects(apiObjType='NewWTG'):
    dummy = calc_park_service.GetTApiWtgId()
    dummy.Handle = w.Handle
    dummy.Rowindex = 0
    wtgids['TApiWtgId'].append(dummy)

# Adding turbines
park_calc.NewWtgs = wtgids
# No existing wtgs
park_calc.ExistWtgs = factory.TApiWtgIds()

nan_to_skipvalue(park_calc)
calc_park_service.SetParkCalc(park_calc)
calculation_service.CloseCalc(save=True)
calculation_service.Calculate(handle=handle_park)

# Exporting PDF report
name = park_calc.Name
calculation_service.SaveReportAsPdf(handle=handle_park,
                                    id=-1,
                                    filename=os.path.join(working_dir, f'Park_{name}.pdf'))

A second example is provided using scripting to repeat PARK calculations to find the marginal AEP for each turbine. This is done by repeating the PARK calculation removing one turbine at a time.

"""
Copyright 2024 EMD International
License for this script: MIT https://opensource.org/license/mit/
License for windPRO commercial software: https://www.emd-international.com/contact-us/general-terms-conditions-sale/
"""

import os
import pandas as pd
from copy import deepcopy
from windproapi.utils import get_windpro_sample_path
from windproapi import nan_to_skipvalue
from windproapi import WindProApi

# Opening windPRO
_windproapi = WindProApi()

working_dir = os.path.join(get_windpro_sample_path('4.1'), 'New Salem', '4.1')
project_path = os.path.join(working_dir, 'New Salem.w41p')

_windproapi.start_windpro_random_port()

# Services
project_service = _windproapi.get_service('ProjectService')
objects_service = _windproapi.get_service('ObjectsService')
calculation_service = _windproapi.get_service('CalculationService')
calc_park_service = _windproapi.get_service('CalcParkService')
obj_wtg_service = _windproapi.get_service('ObjWtgService')
factory = _windproapi.get_factory('WindproService')

# Loading project
project_service.LoadFromFile(filename=project_path)

# Getting a list of all calculations present
for i in range(calculation_service.GetCount()):
    print(calculation_service.GetCalc(ndx=i))

# Getting all PARK calculations in
all_park_calcs = calculation_service.GetCalcs(calcType='CalcPark')

# Choosing one of the PARK calculation, simply by index.
Handle = all_park_calcs[1].Handle
calculation_service.OpenCalcForEdit(handle=Handle)
park_calc = calc_park_service.GetParkCalc()
print(park_calc)
# park_calc.UseCurtailment = True
park_calc.TimevarData.Aggregate = 0
nan_to_skipvalue(park_calc)
calc_park_service.SetParkCalc(park_calc)
calculation_service.CloseCalc(save=True)
calculation_service.Calculate(handle=Handle)

# Make a copy to modify it
cloneHandle = calculation_service.Clone(handle=all_park_calcs[1].Handle)
calculation_service.OpenCalcForEdit(handle=cloneHandle)
clonedParkCalc = calc_park_service.GetParkCalc()

# Setting new wake model type
# You need to check in the wsdl file or try out different settings in windPRO to get to the right naming conventions.
clonedParkCalc.WakeModelType = 'ParkWakeModelNOJensenPark2'
nan_to_skipvalue(clonedParkCalc)

# Setting the with new settings to park calculation and calculating results
calc_park_service.SetParkCalc(clonedParkCalc)
calculation_service.CloseCalc(save=True)
calculation_service.Calculate(handle=cloneHandle)

# Exporting results from the wind farm
path_result = os.path.join(working_dir, 'park_result.csv')
res_to_file = calculation_service.GetResToFiles(handle=Handle)
print(f'Result {res_to_file}')
calculation_service.ResultToFile(handle=Handle, id='Park result', filename=path_result)

df = pd.read_csv(path_result, sep=';', thousands='.', decimal=',', header=1, skiprows=[2], encoding='ISO-8859-1')
print(df[['Free mean wind speed', 'Result', 'Row data/Description']])

# Making park calculations ommiting single turbines
turbine_list = clonedParkCalc.NewWtgs.TApiWtgId

# Looping over all turbines and
for i in range(len(turbine_list)):
    # List of turbines to include
    temp_turbine_list = deepcopy(turbine_list)
    temp_turbine_list.pop(i)

    # Getting user description to identify turbine that is ommitted later.
    obj = objects_service.GetObjectFromHandle(handle=turbine_list[i].Handle)
    label_ommited = obj.UserDescription

    # Opening for edit
    calculation_service.OpenCalcForEdit(handle=cloneHandle)
    temp_park_calc = calc_park_service.GetParkCalc()

    # Changing number of turbines in the calculation
    temp_park_calc.NewWtgs.TApiWtgId = temp_turbine_list
    nan_to_skipvalue(temp_park_calc)

    # Setting the with new settings to park calculation and calculating results
    calc_park_service.SetParkCalc(temp_park_calc)
    calculation_service.CloseCalc(save=True)
    calculation_service.Calculate(handle=cloneHandle)

    # Exporting results from the wind farm
    path_result = os.path.join(working_dir, f'park_result_{i}.csv')
    calculation_service.ResultToFile(handle=cloneHandle, id='Park result', filename=path_result)

    # Reading the files and
    try:
        import pandas as pd

        # CAUTION: this can fail depending on the setup of the computer to use , or . as decimal seperators.
        # Or depending on your language setup the names of the columns might be different.
        df = pd.read_csv(path_result, sep=';', thousands='.', decimal=',', header=1, skiprows=[2],
                         usecols=['Row data/Description', 'Free mean wind speed', 'Result'],
                         dtype={'Row data/Description': str, 'Free mean wind speed': float, 'Result': float}, encoding = 'ISO-8859-1')

        print(f'Calculating for turbines:')
        print(df['Row data/Description'].to_list())
        print(f'Ommiting turbine:')
        print(label_ommited)
        print(df[['Result']].sum())
    except:
        print('Could not read results file.')

Find below an example of a PARK calculation using precalculated resource files.

"""
Copyright 2024 EMD International
License for this script: MIT https://opensource.org/license/mit/
License for windPRO commercial software: https://www.emd-international.com/contact-us/general-terms-conditions-sale/
"""

import os
from copy import deepcopy
from datetime import datetime
from windproapi.utils import get_windpro_sample_path
from windproapi import nan_to_skipvalue
from windproapi import WindProApi

# Opening windPRO
_windproapi = WindProApi()

working_dir = os.path.join(get_windpro_sample_path('4.1'), 'New Salem', '4.1')
project_path = os.path.join(working_dir, 'New Salem.w41p')

_windproapi.start_windpro_random_port()

# Services
project_service = _windproapi.get_service('ProjectService')
objects_service = _windproapi.get_service('ObjectsService')
calculation_service = _windproapi.get_service('CalculationService')
calc_park_service = _windproapi.get_service('CalcParkService')
obj_wtg_service = _windproapi.get_service('ObjWtgService')
factory = _windproapi.get_factory('WindproService')

# Loading project
project_service.LoadFromFile(filename=project_path)

# New PARK calculation
handle_park = calculation_service.CreateEmpty(calcType='CalcPark')

# Opening calculation
calculation_service.OpenCalcForEdit(handle=handle_park)
park_calc = calc_park_service.GetParkCalc()

park_calc.Name = 'resource based from scripting'
park_calc.ParkCalcType = 'ParkTypeResource'

# Making right datatype for setting rsf files
rsf_file_names = factory.TApiStringArray()
rsf_file_names.string = os.path.join(working_dir, 'New Salem_Res_100_Hub_100.0_50.0_80.0_0.rsf')
park_calc.StatData.RSFilenames = rsf_file_names
nan_to_skipvalue(park_calc)
calc_park_service.SetParkCalc(park_calc)

calculation_service.CloseCalc(save=True)
calculation_service.Calculate(park_calc.Handle)

# Exporting PDF report
name = park_calc.Name
calculation_service.SaveReportAsPdf(handle=handle_park,
                                    id=-1,
                                    filename=os.path.join(working_dir, f'Park_{name}.pdf'))