Usage
Building Models with the generation_models
schema
The models used by the Tyba Python Client come from the generation_models
package, which is installed as a dependency
of the client (it’s always a good idea to make sure you have the latest version). The
package provides high-level classes for representing PV solar power plants (PVGenerationModel
), grid-connected
energy storage assets (StandaloneStorageModel
), and hybrid systems (PVStorageModel
). Each instance of these
classes contains all of the information needed to model a renewable asset over some period of time, and can be passed
to the client to schedule a model run and generate results.
The models have a nested class structure, so each attribute of the class is either a direct input or an object that
contains inputs to some submodel. So a PVStorageModel
instance might look like
from generation_models import * model = PVStorageModel( energy_prices=DARTPrices(...), time_interval_mins=30, storage_inputs=MultiStorageInputs( batteries=[ BatteryParams( ..., capacity_degradation_model=TableCapDegradationModel(...) ) ] ), pv_inputs=PVGenerationModel( solar_resource=SolarResource(...), inverter=Inverter(...), pv_module=PVModule(...), system_design=PVSystemDesign( ..., tracking=SingleAxisTracking(...) ), ), storage_coupling=StorageCoupling.dc, )
where ...
represents model inputs that aren’t nested classes. Submodels are also
modular, so different models (with different inputs) can be used interchangeably. For example, the Tyba Client can model
inverters using the OND model (via the ONDInverter
class) or
CEC Model (via the Inverter
class) and
instances of either class can be used anywhere an inverter
attribute is required. Where applicable, “convenience”
inputs are provided to streamline the modeling process, e.g. we can set
PVGenerationModel.solar_resource
to a
SolarResource
instance, or to a tuple of
(latitude, longitude)
. In the latter case, the Tyba API automatically
queries the NSRDB for the relevant solar resource information so we don’t have to
provide it.
Detailed information on all possible models and their inputs can be found on the
Model Schema page of the generation_models documentation, but a good
place to start is the docs for the 3 high level classes:
PVGenerationModel
,
StandaloneStorageModel
, and
PVStorageModel
.
Importing Data from Local Files
We often need to incorporate data from files (e.g. solar resource, prices, equipment parameters) into our models. While
for something like price data we might use
pandas, the
tyba_client.io
module provides
functions for handling OND and PAN files and PSM-formatted solar resource CSVs. For example,
from generation_models import * from tyba_client.io import solar_resource_from_psm_csv solar_resource = solar_resource_from_psm_csv("my_weather.csv", typical=True) model = PVGenerationModel( solar_resource=solar_resource, ... )
Scheduling and Receiving Responses
Because of the computational intensity of model runs, Tyba’s API follows a “Schedule & Receive” process.
Scheduling a Model Run
Once we’ve successfully built our model, we can use the client.schedule
method to send the model to the Tyba servers and request that it be scheduled for completion.
resp = client.schedule(model)
We can assess how our request went by inspecting the
requests.Response object that is returned by
the schedule
method.
resp.raise_for_status() # will raise an error if we got a bad response status code print(resp.json()) # resp.json() is the response message as a dict #returns {"id":"d08b1e8e-6c68-40cf-b29c-0b6da18cd777"} id_ = resp.json()["id"] # we'll use this id to check the run's status
If our request was successful, the response message will contain an id for the scheduled run. If not, the
raise_for_status
method will raise an error and the response message will contain the particular
HTTP status code and more information on the issue.
Receiving and Analyzing Results
To receive the simulation results, we pass the scheduled run’s id to the
client.wait_on_result
method. This
method checks the status of the model run at some interval (customizable by the wait_time
argument) and returns a
results dictionary once the run is complete.
results = client.wait_on_result(id_)Depending on the simulation requested, it might take quite a while for this line of code to execute
The structure of the results
dict will depend on the model that was run, so it’s a good idea to check the keys.
For example, for a PVStorageModel
:
print(results.keys()) # returns # dict_keys(['solar_only', 'solar_storage', 'solar_storage_waterfall', 'optimizer_outputs'])
Below the top level, most of the results are time series that can be converted into pandas DataFrames for in-depth analysis, plotting and easy export. Continuing the example above:
import pandas as pd df = pd.DataFrame(results["solar_storage"]) # with the dataframe we can analyze, summarize, plot etc # we can also export our results df.to_csv("my-results.csv")
Receiving Results in the v1 Format
In order to make results data more consistent across different model types, Tyba has introduced a “v1” results format
that organizes time series outputs according to their corresponding equipment in a “single line diagram”
sense. This format will eventually become the standard for API results, but currently it is offered as an option and
accessible by using the client.wait_on_result_v1
method in place
of the client.wait_on_result
method used in the above example.
Checking the Status of a Scheduled Run
After a simulation run has been scheduled, instead of using client.wait_on_result
, we can check on the status
ourselves by passing the scheduled run’s id to the client.get_status
method. Similar to client.schedule
, a requests.Response object is returned
and the response message will contain a "status"
field that we can inspect:
status = client.get_status(id_).json()["status"] print(status)
Until a run is complete, it’s status will be "scheduled"
, and we must keep checking until the status has changed
to "complete"
. To facilitate this, we can run our status check in a loop:
complete = False while not complete: res = client.get_status(id).json() if res["status"] == "complete": complete = True print(res.keys()) else: time.sleep(2)
This is, in fact, what client.wait_on_result
accomplishes for us. Our status check can also return an
"error"
status. This means an error has occurred during the model run itself. Similar to scheduling, we can
inspect res.json()
to get more insight into what is going on, but it may not always be clear. Tyba is alerted
when run errors occur and will typically reach out to resolve the issue. Of course, please reach out for any
time-sensitive issues. A table of Tyba Model Run Status Codes with summaries is provided below.
Performing Project Finance Calculations
Once we have our results, the project_finance
package (which is installed as a dependency of
the Tyba Client) can be used to perform simple NPV and IRR calculations:
import project_finance as pf res = res["result"] revenue = pd.Series( data=np.array(res["market_awards"]["rtm_base_point"]) * rates.rtm + np.array(res["market_awards"]["dam_base_point"]) * rates.dam, index=pd.period_range(start="1990-01-01T00:00", periods=len(rates.rtm), freq="H")) monthly_revenue = revenue.groupby(revenue.index.month).sum().values inputs = pf.HybridFinanceInputsSingleRev( frequency="monthly", revenue=monthly_revenue, solar=pf.SolarInputs( array_capacity_wdc=model.pv_inputs.system_design.dc_capacity * 10**3, # W capex_cost_per_wdc=0.60, opex_cost_per_wdc=0.007 ), storage=pf.StorageInputs( capacity_kwh=model.storage_inputs[0].energy_capacity, capex_cost_per_kwh=150.00, opex_cost_per_kwh=5.00 ) ) npv = pf.npv(inputs, annual_discount_rate=0.05) irr = pf.irr(inputs) print(f"5% NPV: {npv}") print(f"IRR: {irr}")
Rates Limits
To protect Tyba’s servers from being overwhelmed (such that they degrade service for other users), we impose rate limits on API requests. We monitor performance closely and seek to deliver as fast and reliable computing as we can. These rate limits will never be used to limit reasonable requests, only to protect against runaway or excessive requests. Below we provide an example of designing your code around Tyba’s rate limits, but please reach out to us if you would like additional assistance in that area.
Model Units
The units for the Tyba rate limit is a single design-year. For example, a single-year PV run would be one unit, whereas a 10-year run would be 10 units.
Limit by Model Type
Subject to change
For PV-Only runs
Limit is 5 units per second
For PV+Storage or Standalone Storage runs
Limit is 10 units per minute
One unit typically takes 10-15 seconds to run, so this should not be prohibitive to batched runs.
Example Rate Limit Backoff
def is_ratelimited(response):
return response.status_code==429
def is_unprocessable(response):
return response.status_code==422
def portfolio_schedule(models,save_name):
ids = {}
for project, model in models.items():
print(f"{project} scheduling")
tries = 8
backoff = 2
init = 2
while tries > 0:
res = client.schedule_pv_storage(model)
if is_unprocessable(res):
print("ERROR: could not schedule")
print(res.text)
raise Exception(f"could not process {project}")
if is_ratelimited(res):
print("Call ratelimited...retrying")
time.sleep(init)
init *= backoff
tries -= 1
else:
print(res.text) #this will show the id for that run
run_id = res.json()["id"]
ids[project] = run_id
break
else:
print(f"{project} exceeded tries and has failed.")
return ids
HTTP Response Codes
HTTP Status Code |
Summary |
---|---|
200 - Ok |
Request worked as expected. |
400 - Bad Request |
The request was not accepted. Usually due to a missing required parameter or an incorrect parameter type. |
401 - Unauthorized |
An invalid or missing TYBA_PAT provided. |
402 - Request Failed |
The request failed despite valid parameters. Check to make sure input parameters are the appropriate scale, and would not lead to an infeasible solution. If problem persists, contact Tyba. |
403 - Forbidden |
The TYBA_PAT does not have sufficient permissions to perform this request. |
404 - Not Found |
The requested endpoint does not exist. Check your request. |
422 - Unprocessable Entity |
The request was accepted but was unable to process the instructions. For example, when requesting pricing data, if a pricing year is requested but not in the database, this error will be raised. |
429 - Rate Limited |
Too many requests were scheduled with the API too quickly. Please implement an exponential backoff of your requests. For more information on rate limits, visit this page. |
500-504 - Tyba Server Error |
An error occurred with Tyba’s servers. Please try refreshing and contact Tyba if the issue persists. |
Tyba Model Run Status Codes
Status Code |
Summary |
---|---|
scheduled |
The request has been sent to Tyba’s servers and the model is running. |
completed |
The request has finished. |
unknown |
The requested run id is not currently scheduled. Check your run id and try again. |
error |
The request has failed. Try refreshing, and if issue persists, contact Tyba. |