{ "cells": [ { "cell_type": "markdown", "id": "fafb2bae", "metadata": {}, "source": [ "# How To Use WOMBAT\n", "\n", "This tutorial will walk through the setup, running, and results stages of a WOMBAT simulation.\n", "\n", "## Imports\n", "\n", "The following code block demonstrates a typical setup for working with WOMBAT and running\n", "analyses." ] }, { "cell_type": "code", "execution_count": 1, "id": "1dc64c46", "metadata": {}, "outputs": [], "source": [ "from time import perf_counter # timing purposes only\n", "\n", "import numpy as np\n", "import pandas as pd\n", "\n", "from wombat.core import Simulation\n", "from wombat.core.library import load_yaml, DINWOODIE\n", "\n", "# Seed the random variable for consistently randomized results\n", "np.random.seed(0)\n", "\n", "# Improve ability to read longer outputs\n", "pd.set_option(\"display.float_format\", '{:,.2f}'.format)\n", "pd.set_option(\"display.max_rows\", 1000)\n", "pd.set_option(\"display.max_columns\", 1000)" ] }, { "cell_type": "markdown", "id": "36bcc6d0", "metadata": {}, "source": [ "## Defining the Simulation\n", "\n", "The following will demonstrate the required information to run a simulation. For the\n", "purposes of this tutorial, we'll be working with the data under `library/dinwoodie` in\n", "the Github repository, and specifically the base case.\n", "\n", "````{note}\n", "One important item to note is that the library structure is enforced within the code so all\n", "data must be placed in the appropriate locations in your analysis' library as follows:\n", "```\n", "\n", "├── config <- Simulation configuration files\n", "├── windfarm <- Windfarm layout file(s); turbine, substation, and cable configurations\n", "├── outputs\n", "│ ├── logs <- The raw anaylsis log files\n", "│ ├── metrics <- A place to save metrics/computed outputs.\n", "│ ├── <- Any other folder you choose for saving outputs (not enforced)\n", "├── repair\n", "│ ├── transport <- Servicing equipment configurations\n", "├── weather <- Weather profiles\n", "```\n", "\n", "As a convenience feature you can import the provided validation data libraries as\n", "`DINWOODIE` or `IEA_26` as is seen in the imports above, and a consistent path will be\n", "enabled.\n", "````\n", "\n", "\n", "### Windfarm Layout\n", "\n", "The windfarm layout is determined by a csv file, `dinwoodie/windfarm/layout.csv` in this case. Below is a sample of what information is required and how to use each field, followed by a visual example. It should be noted that none of the headings are case sensitive.\n", "\n", "\n", "id (required)\n", ": Unique identifier for the asset; no spaces allowed.\n", "\n", "substation_id (required)\n", ": The id field for the substation that the asset connects to; in the case that this is a substation, then this field should be the same as id; no spaces allowed.\n", "\n", "name (required)\n", ": A descriptive name for the turbine, if desired. This can be the same as id.\n", "\n", "longitude (optional)\n", ": The longitudinal position of the asset, can be in any geospatial reference; optional.\n", "\n", "latitude (optional)\n", ": The latitude position of the asset, can be in any geospatial reference; optional.\n", "\n", "string (required)\n", ": The integer, zero-indexed, string number for where the turbine will be positioned.\n", "\n", "order (required)\n", ": The integer, zero-indexed position on the string for where the turbine will be positioned.\n", "\n", "distance (optional)\n", ": The distance to the upstream asset; if this is calculated (input = 0), then the straightline distance is calculated using the provided coordinates (WGS-84 assumed).\n", "\n", "subassembly (required)\n", ": The file that defines the asset's modeling parameters.\n", "\n", "upstream_cable (required)\n", ": The file that defines the upstream cable's modeling parameters.\n", "\n", "```{note}\n", "In the example below, there are a few noteworthy caveats that will set the stage for\n", "later input reviews:\n", " - The cables are not modeled, which has a couple of implications\n", " - There only needs to be one string \"0\"\n", " - The cable specifications are required, even if not being modeled (details later)\n", " - longitude, latitude, and distance are all \"0\" because the spatial locations are not used\n", " - subassembly is all \"vestas_v90.yaml\", but having to input the turbine subassembly model\n", " means that multiple turbine types can be used on a windfarm.\n", " - This same logic applies to the upstream_cable so that multiple cable types can be\n", " used as appopriate.\n", "```\n", "\n", "| id | substation_id | name | longitude | latitude | string | order | distance | subassembly | upstream_cable |\n", "| :-- | :-- | :-- | --: | --: | --: | --: | --: | :-- | :-- |\n", "| OSS1 | OSS1 | OSS1 | 0 | 0 | | | | offshore_substation.yaml | export.yaml |\n", "| S00T1 | OSS1 | S00T1 | 0 | 0 | 0 | 0 | 0 | vestas_v90.yaml | array.yaml |\n", "| S00T2 | OSS1 | S00T2 | 0 | 0 | 0 | 1 | 0 | vestas_v90.yaml | array.yaml |\n", "| S00T3 | OSS1 | S00T3 | 0 | 0 | 0 | 2 | 0 | vestas_v90.yaml | array.yaml |\n", "| ... |\n", "| S00T79 | OSS1 | S00T79 | 0 | 0 | 0 | 78 | 0 | vestas_v90.yaml | array.yaml |\n", "| S00T80 | OSS1 | S00T80 | 0 | 0 | 0 | 79 | 0 | vestas_v90.yaml | array.yaml |\n", "\n", "\n", "### Weather Profile\n", "\n", "The weather profile will broadly define the simulation range with its start and stop\n", "points, though a narrower one can be used when defining the simulation (more later).\n", "\n", "The following columns should exist in the data set with the provided guidelines.\n", "datetime (required)\n", ": A date and time stamp, any format.\n", "\n", "windspeed (required)\n", ": Unique identifier for the asset; no spaces allowed.\n", "\n", "waveheight (optional)\n", ": The hourly, mean waveheight, in meters, for the time stamp. If waves are not required,\n", " this can be filled with zeros, or be left out entirely.\n", "\n", "\n", "Below, is a demonstration of what `weather/alpha_ventus_weather_2002_2014.csv` looks like.\n", "\n", "| datetime | windspeed | waveheight |\n", "| :-- | --: | --: |\n", "| 1/1/02 0:00 | 11.75561096 | 1.281772405 |\n", "| 1/1/02 1:00 | 10.41321252 | 1.586584315 |\n", "| 1/1/02 2:00 | 8.959270788 | 1.725690828 |\n", "| 1/1/02 3:00 | 9.10014808 | 1.680982063 |\n", "| ... |\n", "| 12/31/14 22:00 | 14.40838803 | 0.869625003 |\n", "| 12/31/14 23:00 | 14.04563195 | 0.993031445 |\n", "\n", "\n", "### Fixed Costs\n", "\n", "Please see the `FixedCosts` API documentation for details on this optional piece of\n", "financial modeling.\n", "\n", "### Financial Model (SAM)\n", "\n", "WOMBAT uses PySAM to model the financials of the windfarm and retrieve higher-level cost\n", "metrics only data is passed into the `SAM_settings` filed in the main configuration. An\n", "input file exists, but is only for demonstration purposes, and does not contain\n", "meaningful inputs.\n", "\n", "\n", "### The System Models\n", "\n", "The higher-level systems (assets) of a windfarm are the cables, turbine(s), and\n", "substation(s), each with their own model, though heavily overlapping. Within each of\n", "these systems, there are modeled, and pre-defined, subassemblies (or componenents) that\n", "rely on two types of maintenance tasks:\n", " - maintenance: scheduled maintenance tasks\n", " - failures: unscheduled maintenance tasks\n", "\n", "Generally, though in this case for the electrical_system subassembly it is not modeled\n", "and has no inputs, the data inputs look like the below example. For a thorough definition,\n", "it is recommended to see the API documentation of the\n", "[Maintenance](../API/types.md#maintenance-tasks) and\n", "[Failure](../API/types.md#failures) data classes. It should be noted that the yaml\n", "defintion below specifies that maintenance tasks are in a bulleted list format and that\n", "failure defintions require a dictionary-style input with keys to match the severity\n", "level of a given failure.\n", "\n", "```{note}\n", "For all non-modeled parameters, inputs must still be provided, though they can be all\n", "zeros. This may become more flexible over time, but for now, at least on maintenance\n", "and one failure must be provided even when all zeros.\n", "```\n", "\n", "```\n", "electrical_system:\n", " name: electrical_system\n", " maintenance:\n", " - description: n/a\n", " time: 0\n", " materials: 0\n", " service_equipment: CTV\n", " frequency: 0\n", " failures:\n", " 1:\n", " scale: 0\n", " shape: 0\n", " time: 0\n", " materials: 0\n", " service_equipment: CTV\n", " operation_reduction: 0\n", " level: 1\n", " description: n/a\n", "```\n", "\n", "A more complete example would be for the generator subassembly, as follows:\n", "```\n", "generator:\n", " name: generator\n", " maintenance:\n", " - description: annual service\n", " time: 60\n", " materials: 18500\n", " service_equipment: CTV\n", " frequency: 365\n", " failures:\n", " 1:\n", " scale: 0.1333\n", " shape: 1\n", " time: 3\n", " materials: 0\n", " service_equipment: CTV\n", " operation_reduction: 0.0\n", " level: 1\n", " description: manual reset\n", "```\n", "\n", "\n", "#### Substations\n", "\n", "The substation model relies on two specific inputs, and one subassembly input (transformer).\n", "\n", "capacity_kw\n", ": The capacity of the system. Only needed if a $/kw cost basis is being used.\n", "\n", "capex_kw\n", ": The $/kw cost of the machine, if not providing absolute costs.\n", "\n", "transformer\n", ": See the subassembly model for more details.\n", "\n", "The following is taken from `windfarm/offshore_substation.yaml` and is a good example of\n", "a non-modeled system.\n", "\n", "```\n", "capacity_kw: 0\n", "capex_kw: 0\n", "transformer:\n", " name: transformer\n", " maintenance:\n", " -\n", " description: n/a\n", " time: 0\n", " materials: 0\n", " service_equipment: CTV\n", " frequency: 0\n", " failures:\n", " 1:\n", " scale: 0\n", " shape: 0\n", " time: 0\n", " materials: 0\n", " service_equipment: [CTV]\n", " operation_reduction: 0\n", " level: 1\n", " description: n/a\n", "```\n", "\n", "\n", "#### Turbines\n", "\n", "The turbine has the most to define out of the three systems in the windfarm model.\n", "Similar to the substation, it relies mainly on the subsystem model with a few extra\n", "parameters, as defined here:\n", "\n", "capacity_kw\n", ": The capacity of the system. Only needed if a $/kw cost basis is being used.\n", "\n", "capex_kw\n", ": The $/kw cost of the machine, if not providing absolute costs.\n", "\n", "power_curve: file\n", ": File that provides the power curve definition.\n", "\n", "power_curve: bin_width\n", ": Distince in (m/s) between two points on the power curve.\n", "\n", "The `windfarm/vestas_v90.yaml` data file provides the following definition.\n", "\n", "```\n", "capacity_kw: 3000\n", "capex_kw: 1300 # need an updated value\n", "power_curve:\n", " file: vestas_v90_power_curve.csv\n", " bin_width: 0.5\n", "```\n", "\n", "The power curve input CSV contains two columns `windspeed_ms` and `power_kw` that should\n", "be defined using the windspeed for a bin, in m/s and the power produced at that\n", "windspeed, in kW.\n", "\n", "In addition to the above, the following subassembly definitions must be provided in\n", "a similar manner to the substation transformer.\n", "\n", " - electrical_system\n", " - electronic_control\n", " - sensors\n", " - hydraulic_system\n", " - yaw_system\n", " - rotor_blades\n", " - mechanical_brake\n", " - rotor_hub\n", " - gearbox\n", " - generator\n", " - supporting_structure\n", " - drive_train\n", "\n", "\n", "#### Cables\n", "\n", "```{note}\n", "Currently, only array cables are modeled at this point in time, though in the future\n", "they will be enabled.\n", "```\n", "\n", "The array cable is the simplest format in that you only define a descriptive name,\n", "and the maintenance and failure events as below.\n", "\n", "```\n", "name: array cable\n", "maintenance:\n", " -\n", " description: n/a\n", " time: 0\n", " materials: 0\n", " service_equipment: CTV\n", " frequency: 0\n", "failures:\n", " 1:\n", " scale: 0\n", " shape: 0\n", " time: 0\n", " materials: 0\n", " operation_reduction: 0\n", " service_equipment: CAB\n", " level: 1\n", " description: n/a\n", "```\n", "\n", "## Servicing Equipment\n", "\n", "The servicing equipment currently run on an scheduled visit basis for the sake of\n", "simplicity in this early stage of the model. This may seem to be a limitation, there are\n", "plenty of workarounds in this approach, by design! For instance, due to the simplicity\n", "of the input model, we can simulate multiple visits throughout the year for large cranes\n", "(e.g., heavy lift vessels, crew transfer vessels, or crawler cranes) enabling similar\n", "behaviors to that of an unscheduled maintenance model.\n", "\n", "For complete documentation of how the servicing equipment parameters are defined, please\n", "see the [ServiceEquipmentData API documentation](../API/types.md#service-equipment)\n", "\n", "Below is an definition of the different equipment codes and their designations to show\n", "the breadth of what can be simulated.\n", "\n", "RMT\n", ": remote (no actual equipment BUT no special implementation), akin to remote resets\n", "\n", "DRN\n", ": drone, or potentially even helicopters by changing the costs\n", "\n", "CTV\n", ": crew transfer vessel/vehicle\n", "\n", "SCN\n", ": small crane (i.e., field support vessel)\n", "\n", "LCN\n", ": large crane (i.e., heavy lift vessel)\n", "\n", "CAB\n", ": cabling-specific vessel/vehicle\n", "\n", "DSV\n", ": diving support vessel\n", "\n", "In addition to a variety of servicing equipment types, there is support for\n", "3 different equipment-level dispatch strategies, as described below. For a set of\n", "example scenarios, please see the [strategy demonstration](strategy_demonstration.ipynb).\n", "\n", "scheduled\n", ": dispatch servicing equipment for a specified date range each year\n", "\n", "requests\n", ": dispatch the servicing equipment once a `strategy_threshold` number of requests\n", " that the equipment can service has been reached\n", "\n", "downtime\n", ": dispatch the servicing equipment once the windfarm's operating level reaches the\n", " `strategy_threshold` percent operating has been reached.\n", "\n", "\n", "## Set Up the Simulation\n", "\n", "In the remaining sections of the tutorial, we will work towards setting up and running\n", "a simulation.\n", "\n", "### Define the data library path\n", "\n", "The set library enables WOMBAT to easily access and store data files in a consistent\n", "manner. Here the `DINWOODIE` reference is going to be used again." ] }, { "cell_type": "code", "execution_count": 2, "id": "51584b36", "metadata": {}, "outputs": [], "source": [ "library_path = DINWOODIE" ] }, { "cell_type": "markdown", "id": "65bb6a1c", "metadata": {}, "source": [ "### Load the configuration file\n", "\n", "```{note}\n", "At this stage, the path to the configuration will need to be created manually.\n", "```\n", "\n", "In this configuration we've provide a number of data points that will define our windfarm layout, weather conditions, working hours, customized start and completion years, project size, financials, and the servicing equipment to be used. Note that there can be as many or as\n", "few of the servicing equipment as desired." ] }, { "cell_type": "code", "execution_count": 3, "id": "92901b55", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[1mname\u001b[0m:\n", " dinwoodie_base\n", "\u001b[1mlibrary\u001b[0m:\n", " DINWOODIE\n", "\u001b[1mweather\u001b[0m:\n", " alpha_ventus_weather_2002_2014.csv\n", "\u001b[1mservice_equipment\u001b[0m:\n", " ['ctv1.yaml', 'ctv2.yaml', 'ctv3.yaml', 'fsv_scheduled.yaml', 'hlv_1_scheduled.yaml', 'hlv_2_scheduled.yaml', 'hlv_3_scheduled.yaml']\n", "\u001b[1mlayout\u001b[0m:\n", " layout.csv\n", "\u001b[1minflation_rate\u001b[0m:\n", " 0\n", "\u001b[1mfixed_costs\u001b[0m:\n", " fixed_costs.yaml\n", "\u001b[1mworkday_start\u001b[0m:\n", " 7\n", "\u001b[1mworkday_end\u001b[0m:\n", " 19\n", "\u001b[1mstart_year\u001b[0m:\n", " 2003\n", "\u001b[1mend_year\u001b[0m:\n", " 2012\n", "\u001b[1mproject_capacity\u001b[0m:\n", " 240\n" ] } ], "source": [ "config = load_yaml(str(library_path / \"config\"), \"base.yaml\")\n", "\n", "for k, v in config.items():\n", " print(f\"\\033[1m{k}\\033[0m:\\n {v}\") # make the keys bold" ] }, { "cell_type": "markdown", "id": "53013eb2", "metadata": {}, "source": [ "## Instantiate the simulation\n", "\n", "There are two ways that this could be done, the first is to use the classmethod `Simulation.from_config()`, which allows for the full path string, a dictionary, or ``Configuration`` object to passed as an input, and the second is through a standard class initialization." ] }, { "cell_type": "code", "execution_count": 4, "id": "a126491e", "metadata": {}, "outputs": [], "source": [ "# Option 1\n", "sim = Simulation.from_config(config)\n", "\n", "# Delete the .log files that get initialized\n", "sim.env.cleanup_log_files(log_only=True)\n", "\n", "# Option 2\n", "# Note here that a string \"DINWOODIE\" is passed because the Simulation class knows to\n", "# retrieve the appropriate path, and that the simulation_name matches the configuration\n", "simulation_name = \"dinwoodie_base\"\n", "sim = Simulation(\n", " name=simulation_name,\n", " library_path=\"DINWOODIE\",\n", " config=\"base.yaml\"\n", ")" ] }, { "cell_type": "markdown", "id": "4617ea04", "metadata": {}, "source": [ "```{note}\n", "In Option 2, the config parameter can also be set with a dictionary.\n", "```\n", "\n", "\n", "## Run the analysis\n", "\n", "When the run method is called, the default run time is for the full length of the simulation, however, if a shorter run than was previously designed is required for debugging, or something similar, we can use `sum.run(until=)` to do this. In the `run` method, not\n", "only is the simulation run, but the metrics class is loaded at the end to quickly transition\n", "to results aggregation without any further code.\n", "\n", "```{warning}\n", "It should be noted at this stage that if a PySAM input file is specified AND a run time that isn't divisible by 8760 (hours in a year), the run will fail at the end due to PySAM's requirements. This will be worked out in later iterations to remove both leap years present in the data (currently available) and remainder hours to cap it to the correct number of hours (future feature).\n", "\n", "Users should also be careful of leap years because the PySAM model cannot handle them,\n", "though if Feb 29 is provided, it will be a part of the analysis and stripped out before\n", "being fed to PySAM.\n", "```" ] }, { "cell_type": "code", "execution_count": 5, "id": "100b45da", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Run time: 3.92 minutes\n" ] } ], "source": [ "# Timing for a demonstration of performance\n", "start = perf_counter()\n", "\n", "sim.run()\n", "\n", "end = perf_counter()\n", "\n", "timing = end - start\n", "if timing > 60 * 2:\n", " print(f\"Run time: {timing / 60:,.2f} minutes\")\n", "else:\n", " print(f\"Run time: {timing:,.4f} seconds\")" ] }, { "cell_type": "markdown", "id": "d9b585fe", "metadata": {}, "source": [ "## Metric computation\n", "\n", "For a more complete view of what metrics can be compiled, please see the [metrics notebook](metrics_demonstration.ipynb), though for the sake of demonstration a few methods will\n", "be shown here" ] }, { "cell_type": "code", "execution_count": 6, "id": "629a6889", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " Net Capacity Factor: 0.4%\n", "Gross Capacity Factor: 0.5%\n" ] } ], "source": [ "net_cf = sim.metrics.capacity_factor(which=\"net\", frequency=\"project\", by=\"windfarm\")\n", "gross_cf = sim.metrics.capacity_factor(which=\"gross\", frequency=\"project\", by=\"windfarm\")\n", "print(f\" Net Capacity Factor: {net_cf:2.1f}%\")\n", "print(f\"Gross Capacity Factor: {gross_cf:2.1f}%\")" ] }, { "cell_type": "code", "execution_count": 7, "id": "b6c9a167", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " Project time-based availability: 90.4%\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Project energy-based availability: 90.3%\n", " Project equipment costs: $535,934.98/MW\n" ] } ], "source": [ "# Report back a subset of the metrics\n", "total = sim.metrics.time_based_availability(frequency=\"project\", by=\"windfarm\")\n", "print(f\" Project time-based availability: {total * 100:.1f}%\")\n", "\n", "total = sim.metrics.production_based_availability(frequency=\"project\", by=\"windfarm\")\n", "print(f\"Project energy-based availability: {total * 100:.1f}%\")\n", "\n", "total = sim.metrics.equipment_costs(frequency=\"project\", by_equipment=False)\n", "print(f\" Project equipment costs: ${total / sim.metrics.project_capacity:,.2f}/MW\")\n" ] }, { "cell_type": "markdown", "id": "3e4b8dde", "metadata": {}, "source": [ "## Optional: Delete the logging files\n", "\n", "In the case that a lot of simulations are going to be run, and the processed outputs are all that is required, then there is a convenience method to cleanup these files automatically once you are done." ] }, { "cell_type": "code", "execution_count": 8, "id": "5a00baa0", "metadata": {}, "outputs": [], "source": [ "sim.env.cleanup_log_files(log_only=False)" ] } ], "metadata": { "jupytext": { "text_representation": { "extension": ".md", "format_name": "myst" } }, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.8" }, "source_map": [ 10, 21, 37, 422, 424, 435, 440, 446, 462, 482, 495, 503, 510, 521, 528 ] }, "nbformat": 4, "nbformat_minor": 5 }