From 21ebf5f906198cd83c7fdf1878e01c29ac6cf5c8 Mon Sep 17 00:00:00 2001 From: RHammond2 <13874373+RHammond2@users.noreply.github.com> Date: Mon, 23 Oct 2023 11:53:14 -0700 Subject: [PATCH 1/2] depreacte configuration library requirement --- CHANGELOG.md | 2 ++ docs/source/examples/how_to.md | 2 +- wombat/core/simulation_api.py | 27 +++++++++------------------ 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0baca9b8..04d60523 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - `plot_farm_availability` plots a line chart of the monthly overall windfarm availability. Additional toggles allow for the plotting of individual turbines in the background and/or the a 95% confidence interval band around the farm's availability - `plot_detailed_availability` plots a heatmap of the hourly turbine and farm operational levels to help in debugging simulations where availability may be running suspiciously low (i.e., a turbine might have shut down or a cable failure didn't get repaired within a reasonable time frame). - `Simulation.service_equipment` is now a dictionary to provide clear access to the servicing equipment details. +- `Simulation.from_config()` requires both a library input and a file name input for the configuration after deprecating the `library` field from the configuration requirements due to it being cumbersome ### Methodology Updates @@ -29,6 +30,7 @@ ### Deprecations - The original library structure has been fully deprecated, please see the Reference/How To example for more details on converting data that has not yet been updated. +- The `Simulation.config` no longer requires the `library` field to reduce conflicts with sharing data between users. ## v0.8.1 (28 August 2023) diff --git a/docs/source/examples/how_to.md b/docs/source/examples/how_to.md index 8c97fa8d..2bfd3bd6 100644 --- a/docs/source/examples/how_to.md +++ b/docs/source/examples/how_to.md @@ -539,7 +539,7 @@ Load the file from the `Configuration` object that was created in the prior code ```{code-cell} ipython3 -sim = Simulation.from_config(config) +sim = Simulation.from_config(library_path, config) # Delete any files that get initialized through the simulation environment sim.env.cleanup_log_files() diff --git a/wombat/core/simulation_api.py b/wombat/core/simulation_api.py index ae9607f5..2b77f7be 100644 --- a/wombat/core/simulation_api.py +++ b/wombat/core/simulation_api.py @@ -51,9 +51,6 @@ class Configuration(FromDictMixin): ---------- name: str Name of the simulation. Used for logging files. - library : str - The data directory. See ``wombat.simulation.WombatEnvironment`` for more - details. layout : str The windfarm layout file. See ``wombat.Windfarm`` for more details. service_equipment : str | list[str] @@ -122,7 +119,6 @@ class Configuration(FromDictMixin): """ name: str - library: Path = field(converter=_library_mapper) layout: str service_equipment: str | list[str] = field(converter=convert_to_list) weather: str | pd.DataFrame @@ -217,14 +213,10 @@ def _create_configuration( "dictionary, or ``Configuration`` object!", ) - if self.config.library != self.library_path: - raise ValueError( - f"`library_path`: {self.library_path} and the library in `config`:" - f" {self.config.library} do not match!" - ) - @classmethod - def from_config(cls, config: str | Path | dict | Configuration): + def from_config( + cls, library: str | Path, config: str | Path | dict | Configuration + ): """Creates the ``Simulation`` object only the configuration contents as either a full file path to the configuration file, a dictionary of the configuration contents, or pre-loaded ``Configuration`` object. @@ -249,10 +241,9 @@ def from_config(cls, config: str | Path | dict | Configuration): Simulation A ready-to-run ``Simulation`` object. """ + library = _library_mapper(library) if isinstance(config, (str, Path)): - config = Path(config).resolve() - if TYPE_CHECKING: - assert isinstance(config, Path) # mypy helper + config = library / "project" / "config" / config config = load_yaml(config.parent, config.name) if isinstance(config, dict): config = Configuration.from_dict(config) @@ -263,7 +254,7 @@ def from_config(cls, config: str | Path | dict | Configuration): if TYPE_CHECKING: assert isinstance(config, Configuration) # mypy helper return cls( # type: ignore - library_path=config.library, + library_path=library, config=config, random_seed=config.random_seed, random_generator=config.random_generator, @@ -272,7 +263,7 @@ def from_config(cls, config: str | Path | dict | Configuration): def _setup_simulation(self): """Initializes the simulation objects.""" self.env = WombatEnvironment( - self.config.library, + self.library_path, self.config.weather, simulation_name=self.config.name, workday_start=self.config.workday_start, @@ -371,7 +362,7 @@ def initialize_metrics(self) -> None: self.windfarm.system(t).capacity for t in self.windfarm.turbine_id ] self.metrics = Metrics( - data_dir=self.config.library, + data_dir=self.library_path, events=events, operations=operations, potential=power_potential, @@ -395,7 +386,7 @@ def save_metrics_inputs(self) -> None: for s_id, dict in self.windfarm.substation_turbine_map.items() } data = { - "data_dir": str(self.config.library), + "data_dir": str(self.library_path), "events": str(self.env.events_log_fname), "operations": str(self.env.operations_log_fname), "potential": str(self.env.power_potential_fname), From 086dc0f816145279597ca3fbface5aee465a9ea3 Mon Sep 17 00:00:00 2001 From: RHammond2 <13874373+RHammond2@users.noreply.github.com> Date: Mon, 23 Oct 2023 12:02:58 -0700 Subject: [PATCH 2/2] update documentation --- docs/jupyter_execute/examples/how_to.ipynb | 1701 ++++++++++---------- docs/source/examples/how_to.md | 4 +- wombat/core/simulation_api.py | 12 +- 3 files changed, 822 insertions(+), 895 deletions(-) diff --git a/docs/jupyter_execute/examples/how_to.ipynb b/docs/jupyter_execute/examples/how_to.ipynb index 7a757b07..1149b28e 100644 --- a/docs/jupyter_execute/examples/how_to.ipynb +++ b/docs/jupyter_execute/examples/how_to.ipynb @@ -1,898 +1,823 @@ { - "cells": [ - { - "cell_type": "markdown", - "id": "0e2fa87a", - "metadata": {}, - "source": [ - "# How To Use WOMBAT\n", - "\n", - "This tutorial will walk through the setup, running, and results stages of a WOMBAT\n", - "simulation while providing background information about how each component is related.\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": "acd81625", - "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 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 the legibility of DataFrames\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": "00754a46", - "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\n", - "`library/code_comparison/dinwoodie` in the Github repository, and specifically the base\n", - "case.\n", - "\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", - "```{warning}\n", - "As of v0.6, the following structure will be adopted to mirror the format of the\n", - "[ORBIT library structure](https://github.com/WISDEM/ORBIT/blob/master/ORBIT/core/library.py#L7-L24)\n", - "to increase compatibility between similar libraries.\n", - "\n", - "WOMBAT will still support the original structure until v0.9, depending to allow users to\n", - "update in their own time.\n", - "```\n", - "\n", - "To help users convert to the new structure, the following method is provided to create\n", - "the required folder structure for users.\n", - "\n", - "```{code-block} python\n", - "\n", - "from wombat import create_library_structure # located in wombat.core.library\n", - "\n", - "new_library = \"library\"\n", - "create_library_structure(new_library)\n", - "```\n", - "\n", - "The above method call will produce the below folder and subfolder structure.\n", - "\n", - "```\n", - "\n", - " ├── project\n", - " ├── config <- Project-level configuration files\n", - " ├── port <- Port configuration files\n", - " ├── plant <- Wind farm layout files\n", - " ├── cables <- Export and Array cable configuration files\n", - " ├── substations <- Substation configuration files\n", - " ├── turbines <- Turbine configuration and power curve files\n", - " ├── vessels <- Land-based and offshore servicing equipment configuration files\n", - " ├── weather <- Weather profiles\n", - " ├── results <- The analysis log files and any saved output data\n", - "```\n", - "\n", - "Users can then migrate their data from the old (below) structure to the new (above)\n", - "structure using their preferred method.\n", - "\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 <- Overarching folder for repair configurations, such as ports\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", - "In practice, any folder location can be used so long as it follows the subfolder structure provided\n", - "here.\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", - "type (optional)\n", - ": One of \"turbine\" or \"substation\". This is required to accurately model a multi-substation wind farm. The base assumption is that a substation connects to itself as a means to model the export cable connecting to the interconnection point, however, this is not always the case, as substations may be connected through their export systems. Using this filed allows for that connection to be modeled accurately.\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", - "upstream_cable_name (optional)\n", - ": The descriptive name to give to the cable that will be used during logging. This enables users to use a single cable definition file while maintaining the naming conventions used for the wind farm being simulated.\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", - "
\n", - "\n", - "| id | substation_id | name | type | longitude | latitude | string | order | distance | subassembly | upstream_cable |\n", - "| :-- | :-- | :-- | :-- | --: | --: | --: | --: | --: | :-- | :-- |\n", - "| OSS1 | OSS1 | OSS1 | substation | 0 | 0 | | | | offshore_substation.yaml | export.yaml |\n", - "| S00T1 | OSS1 | S00T1 | turbine | 0 | 0 | 0 | 0 | 0 | vestas_v90.yaml | array.yaml |\n", - "| S00T2 | OSS1 | S00T2 | turbine | 0 | 0 | 0 | 1 | 0 | vestas_v90.yaml | array.yaml |\n", - "| S00T3 | OSS1 | S00T3 | turbine | 0 | 0 | 0 | 2 | 0 | vestas_v90.yaml | array.yaml |\n", - "| ... |\n", - "| S00T79 | OSS1 | S00T79 | turbine | 0 | 0 | 0 | 78 | 0 | vestas_v90.yaml | array.yaml |\n", - "| S00T80 | OSS1 | S00T80 | turbine | 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", - "\n", - "datetime (required)\n", - ": A date and time stamp, any format.\n", - "\n", - "windspeed (required)\n", - ": The hourly, mean windspeed, in meters per second at the time stamp.\n", - "\n", - "waveheight (optional)\n", - ": The hourly, mean waveheight, in meters, at 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", - "### Environmental Considerations\n", - "\n", - "In addition to using weather conditions for site characteristics, WOMBAT is able to\n", - "model environmental considerations where a port or site cannot be accessed, for example,\n", - "when silt builds up and water depths become too low to tow a turbine into the port.\n", - "There is also a feature for imposing maximum operating speeds, such as when there are\n", - "animal migrations and vessels must slow down to avoid collisions with endangered species.\n", - "\n", - "Defining the `non_operational_start` and `non_operational_end` at either the servicing\n", - "equipment, environment, or port level allows for the creation of an annualized date\n", - "range spanning the length of the simulation where operations are not allowed to occur.\n", - "When defined at the environment level, all servicing equipment and a port, if defined,\n", - "will have this non-operational period applied, and if it's already existing, the more\n", - "conservative of the date ranges will be applied. When defined at the port level, all\n", - "associated servicing equipment (tugboats) will have the same inuring priority as when\n", - "defined at the environment level.\n", - "\n", - "The same logic applies when defining the `reduced_speed_start` and `reduced_speed_end`\n", - "for defining when the operating speeds of servicing equipment are capped at the\n", - "`reduced_speed`. As is the case above, these variables can also be defined at the\n", - "servicing equipment level for further customization.\n", - "\n", - "\n", - "### Fixed Costs\n", - "\n", - "Please see the [`FixedCosts` API documentation](../API/types.md#fixed-cost-model) for\n", - "details on this optional piece of financial modeling.\n", - "\n", - "For modeling a tow-to-port strategy that the port rental costs should be included in\n", - "this category, and not in the port configuration.\n", - "\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", - "```{warning}\n", - "This does not currently work due to updates in PySAM that need to be remapped in the\n", - "post-processing module.\n", - "\n", - "Update: This functionality will be entirely deprecated in v0.9.\n", - "```\n", - "\n", - "\n", - "### Servicing Equipment\n", - "\n", - "The servicing equipment control the actual repairs within a simulation, and as of v0.5,\n", - "there are four different repair strategies that can be used: scheduled, downtime-based\n", - "unscheduled, repair-based unscheduled, and tow-to-port. These are options are controlled\n", - "through the `capability` settings in each equipment's configuration in conjunction with\n", - "the `service_equipment` setting in the maintenance and failure configurations for each\n", - "subassembly.\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. These codes do not have separate operating models,\n", - "but instead allow the user to specify the types of operations the servicing equipment\n", - "will be able to operate on. This model should be aligned with the `service_equipment`\n", - "requirements in the subassembly failure and maintenance models.\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/onsite truck\n", - "\n", - "SCN\n", - ": small crane (i.e., field support vessel or cherry picker)\n", - "\n", - "LCN\n", - ": large crane (i.e., heavy lift vessel or crawler crane)\n", - "\n", - "CAB\n", - ": cabling-specific vessel/vehicle\n", - "\n", - "DSV\n", - ": diving support vessel\n", - "\n", - "TOW\n", - ": tugboat/towing (a failure with this setting will trigger a tow-to-port scenario where\n", - "the simulation's `Port` will dispatch the tugboats as needed)\n", - "\n", - "AHV\n", - ": anchor handling vessel (this is a variation on the tugboat for mooring repairs that\n", - "will not tow anthing between port and site, but operate at the site)\n", - "\n", - "Aside from the TOW and AHV capabilities there are no operations specific to each\n", - "capability. The remaining configurations of a servicing equipment such as equipment\n", - "rates, mobilization, labor, and operating limits will define the nature of its operation\n", - "in tandem with a failure's `time` field. So for a remote reset (RMT), there will be a\n", - "trivial equipment rate, if any, associated with it to account for the specific operations\n", - "or resetting the subassembly remotely. Similarly, a drone repair or inspection (DRN)\n", - "will not require any onboard crew, so labor will 0, or low if assuming an operator that\n", - "is unaccounted for in the site `FixedCosts`, but will require more time than a remote\n", - "reset in addition to a higher equipment cost.\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 downtime.\n", - "\n", - "\n", - "### The System Models\n", - "\n", - "The actual assets on the windfarm such as cables, turbines, and substations, are\n", - "referred to as systems in WOMBAT, and each has their own individual model. Within each\n", - "of these systems, there are user-defined subassemblies (or componenents) that rely on\n", - "two types of repair models:\n", - "\n", - "- maintenance: scheduled, fixed time interval-based maintenance tasks\n", - "- failures: unscheduled, Weibull distribution-based modeled, maintenance tasks\n", - "\n", - "The subassemblies keys in the system YAML definition can be user-defined\n", - "to accommodate the varying language among industry groups and generations of wind\n", - "technologies. The only restrictions are the YAML keys must not contain any special\n", - "characters, and cannot be repeated\n", - "\n", - "In the example below we show a `generator` subassembly with an annual service task and\n", - "frequent manual reset. For a thorough definition, please read the API\n", - "documentation of the [Maintenance](../API/types.md#maintenance-tasks) and\n", - "[Failure](../API/types.md#failures) data classes. Note that the yaml defintion below\n", - "specifies that maintenance tasks are in a bulleted list format and that failure\n", - "defintions require a dictionary-style input with keys to match the severity level of a\n", - "given failure. For more details on the complete subassembly definition, please visit the\n", - "[Subassembly API documentation](../API/types.md#subassembly-model).\n", - "\n", - "```{code-block} yaml\n", - "generator:\n", - " name: generator # doesn't need to match the subassembly key that becomes System.id\n", - " maintenance:\n", - " - description: annual service\n", - " time: 60\n", - " materials: 18500\n", - " service_equipment: CTV\n", - " frequency: 365\n", - " operation_reduction: 0 # default value\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", - " replacement: False # default value\n", - " level: 1 # Note that the \"level\" value matches the key \"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 all turbines in the windfarm, neglecting any losses. Only needed if\n", - "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", - "Additional keys can be added to represent subassemblies, such as a transformer, in the\n", - "same format as the generator example above. Similarly, a user can define as man or as\n", - "few of the subassemblies as desired with their preferred naming conventions\n", - "\n", - "The following is an example of substation YAML definition with no modeled subasemblies.\n", - "\n", - "```{code-block} yaml\n", - "capacity_kw: 670000\n", - "capex_kw: 140\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 subassembly 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", - "The desired interval, in m/s, between adjacent points on the power curve to be used for\n", - "power calculations.\n", - "\n", - "The `windfarm/vestas_v90.yaml` data file provides the following definition in addition\n", - "to the the maintenance and failure definitions that were shown previously.\n", - "\n", - "```{code-block} yaml\n", - "capacity_kw: 3000\n", - "capex_kw: 1300\n", - "power_curve:\n", - " file: vestas_v90_power_curve.csv\n", - " bin_width: 0.5\n", - "```\n", - "\n", - "The power curve input CSV requires the following two columns: `windspeed_ms` and\n", - "`power_kw` that should be defined using the windspeed for a bin, in m/s and the power produced at that\n", - "windspeed, in kW. The current method available for generating the power curve is the IEC\n", - "61400-12-1-2 method for a wind-speed binned power curve. If there is a need/desire for\n", - "additional power curve methodologies, then [please submit an issue on the GitHub](https://github.com/WISDEM/WOMBAT/issues)!\n", - "\n", - "For an open source listing of a variety of land-based, offshore, and distributed wind\n", - "turbine power curves, please visit the\n", - "[NREL Turbine Models repository](https://github.com/NREL/turbine-models).\n", - "\n", - "\n", - "#### Cables\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. It should be noted that the scheme\n", - "is a combination of both the system and subassembly configuration.\n", - "\n", - "For export cables that connect substations, as opposed to an interconnection, they will\n", - "not create dependencies because it is assumed they bypass the substation altogether,\n", - "and connect directly with the export system. This means that if there is a failure at a\n", - "downstream substation, the connecting export cable and its upstream turbine, substation,\n", - "and cable connections will continue to operate normally.\n", - "\n", - "```{code-block} yaml\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", - "\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.\n", - "\n", - "```{note}\n", - "If a custom library is being used, the `library_path` must be the full path name to the\n", - "location of the folder where the configuration data is contained.\n", - "```\n", - "\n", - "```{warning}\n", - "In v0.6, a new library structure\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "286d0143", - "metadata": {}, - "outputs": [], - "source": [ - "library_path = DINWOODIE # or user-defined path for an external data library" - ] - }, - { - "cell_type": "markdown", - "id": "a3644fac", - "metadata": {}, - "source": [ - "### The configuration file\n", - "\n", - "\n", - "In the configuration below, there are a number of data points that will define our\n", - "windfarm layout, weather conditions, working hours, customized start and completion\n", - "years, project size, financials, and the servicing equipment to be used. Note that there\n", - "can be as many or as few of the servicing equipment units as desired.\n", - "\n", - "The purpose of an overarching configuration file is to provide a single place to define\n", - "the primary inputs for a simulation. Below the base configuration is loaded and displayed\n", - "with comments to show where each of files are located in the library structure. WOMBAT\n", - "will know where to go for these pointers when the simulation is initialized so the data\n", - "is constructed and validated correctly." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "08d23374", - "metadata": {}, - "outputs": [], - "source": [ - "config = load_yaml(library_path / \"project/config\", \"base.yaml\")" - ] - }, - { - "cell_type": "markdown", - "id": "17cb9344", - "metadata": {}, - "source": [ - "```{code-block} yaml\n", - "# Contents of: dinwoodie / config / base.yaml\n", - "name: dinwoodie_base\n", - "library: DINWOODIE\n", - "weather: alpha_ventus_weather_2002_2014.csv # located in: dinwoodie / weather\n", - "service_equipment:\n", - "# YAML-encoded list, but could also be created in standard Python list notation with\n", - "# square brackets: [ctv1.yaml, ctv2.yaml, ..., hlv_requests.yaml]\n", - "# All below equipment configurations are located in: dinwoodie / vessels\n", - " - ctv1.yaml\n", - " - ctv2.yaml\n", - " - ctv3.yaml\n", - " - fsv_requests.yaml\n", - " - hlv_requests.yaml\n", - "layout: layout.csv # located in: dinwoodie / windfarm\n", - "inflation_rate: 0\n", - "fixed_costs: fixed_costs.yaml # located in: dinwoodie / project / config\n", - "workday_start: 7\n", - "workday_end: 19\n", - "start_year: 2003\n", - "end_year: 2012\n", - "project_capacity: 240\n", - "# port: base_port.yaml <- When running a tow-to-port simulation the port configuration\n", - "# pointer is provided here and located in: dinwoodie / project / port\n", - "```\n", - "\n", - "## Create a simulation\n", - "\n", - "There are two ways that this could be done, the first is to use the classmethod\n", - "`Simulation.from_config()`, which allows for the full path string, a dictionary, or\n", - "`Configuration` object to passed as an input, and the second is through a standard\n", - "class initialization.\n", - "\n", - "### Option 1: `Simulation.from_config()`\n", - "\n", - "Load the file from the `Configuration` object that was created in the prior code black" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "2db905a2", - "metadata": {}, - "outputs": [ + "cells": [ { - "ename": "TypeError", - "evalue": "(\"'rng' must be (got that is a ).\", Attribute(name='rng', default=NOTHING, validator=>, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type='np.random._generator.Generator', converter=None, kw_only=False, inherited=False, on_setattr=None, alias='rng'), , )", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[4], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m sim \u001b[38;5;241m=\u001b[39m \u001b[43mSimulation\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfrom_config\u001b[49m\u001b[43m(\u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 3\u001b[0m \u001b[38;5;66;03m# Delete any files that get initialized through the simulation environment\u001b[39;00m\n\u001b[1;32m 4\u001b[0m sim\u001b[38;5;241m.\u001b[39menv\u001b[38;5;241m.\u001b[39mcleanup_log_files()\n", - "File \u001b[0;32m~/GitHub_Public/WOMBAT/wombat/core/simulation_api.py:279\u001b[0m, in \u001b[0;36mSimulation.from_config\u001b[0;34m(cls, config)\u001b[0m\n\u001b[1;32m 277\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m TYPE_CHECKING:\n\u001b[1;32m 278\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(config, Configuration) \u001b[38;5;66;03m# mypy helper\u001b[39;00m\n\u001b[0;32m--> 279\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mcls\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# type: ignore\u001b[39;49;00m\n\u001b[1;32m 280\u001b[0m \u001b[43m \u001b[49m\u001b[43mlibrary_path\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mconfig\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mlibrary\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 281\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 282\u001b[0m \u001b[43m \u001b[49m\u001b[43mrandom_seed\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mcls\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrandom_seed\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 283\u001b[0m \u001b[43m \u001b[49m\u001b[43mrandom_generator\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mcls\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrandom_generator\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 284\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m:9\u001b[0m, in \u001b[0;36m__init__\u001b[0;34m(self, library_path, config, random_seed, random_generator)\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m _config\u001b[38;5;241m.\u001b[39m_run_validators \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[1;32m 8\u001b[0m __attr_validator_config(\u001b[38;5;28mself\u001b[39m, __attr_config, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mconfig)\n\u001b[0;32m----> 9\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m__attrs_post_init__\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/GitHub_Public/WOMBAT/wombat/core/simulation_api.py:190\u001b[0m, in \u001b[0;36mSimulation.__attrs_post_init__\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 188\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__attrs_post_init__\u001b[39m(\u001b[38;5;28mself\u001b[39m) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m 189\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Post-initialization hook.\"\"\"\u001b[39;00m\n\u001b[0;32m--> 190\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_setup_simulation\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/GitHub_Public/WOMBAT/wombat/core/simulation_api.py:306\u001b[0m, in \u001b[0;36mSimulation._setup_simulation\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 288\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39menv \u001b[38;5;241m=\u001b[39m WombatEnvironment(\n\u001b[1;32m 289\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mconfig\u001b[38;5;241m.\u001b[39mlibrary,\n\u001b[1;32m 290\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mconfig\u001b[38;5;241m.\u001b[39mweather,\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 303\u001b[0m random_generator\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mrandom_generator,\n\u001b[1;32m 304\u001b[0m )\n\u001b[1;32m 305\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mrepair_manager \u001b[38;5;241m=\u001b[39m RepairManager(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39menv)\n\u001b[0;32m--> 306\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mwindfarm \u001b[38;5;241m=\u001b[39m \u001b[43mWindfarm\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43menv\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mconfig\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mlayout\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrepair_manager\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 308\u001b[0m \u001b[38;5;66;03m# Create the servicing equipment and set the necessary environment variables\u001b[39;00m\n\u001b[1;32m 309\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mservice_equipment \u001b[38;5;241m=\u001b[39m []\n", - "File \u001b[0;32m~/GitHub_Public/WOMBAT/wombat/windfarm/windfarm.py:40\u001b[0m, in \u001b[0;36mWindfarm.__init__\u001b[0;34m(self, env, windfarm_layout, repair_manager)\u001b[0m\n\u001b[1;32m 38\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mconfigs: \u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, \u001b[38;5;28mdict\u001b[39m] \u001b[38;5;241m=\u001b[39m {\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mturbine\u001b[39m\u001b[38;5;124m\"\u001b[39m: {}, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124msubstation\u001b[39m\u001b[38;5;124m\"\u001b[39m: {}, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcable\u001b[39m\u001b[38;5;124m\"\u001b[39m: {}}\n\u001b[1;32m 39\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_create_graph_layout(windfarm_layout)\n\u001b[0;32m---> 40\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_create_turbines_and_substations\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 41\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_create_cables()\n\u001b[1;32m 42\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mcapacity: \u001b[38;5;28mint\u001b[39m \u001b[38;5;241m|\u001b[39m \u001b[38;5;28mfloat\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[38;5;28msum\u001b[39m(\n\u001b[1;32m 43\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msystem(turb)\u001b[38;5;241m.\u001b[39mcapacity \u001b[38;5;28;01mfor\u001b[39;00m turb \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mturbine_id\n\u001b[1;32m 44\u001b[0m )\n", - "File \u001b[0;32m~/GitHub_Public/WOMBAT/wombat/windfarm/windfarm.py:183\u001b[0m, in \u001b[0;36mWindfarm._create_turbines_and_substations\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 180\u001b[0m bad_data_location_messages\u001b[38;5;241m.\u001b[39mappend(message)\n\u001b[1;32m 181\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mconfigs[node_type][name] \u001b[38;5;241m=\u001b[39m subassembly_dict\n\u001b[0;32m--> 183\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mgraph\u001b[38;5;241m.\u001b[39mnodes[system_id][\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124msystem\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m=\u001b[39m \u001b[43mSystem\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 184\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43menv\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 185\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrepair_manager\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 186\u001b[0m \u001b[43m \u001b[49m\u001b[43msystem_id\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 187\u001b[0m \u001b[43m \u001b[49m\u001b[43mdata\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mname\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 188\u001b[0m \u001b[43m \u001b[49m\u001b[43msubassembly_dict\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 189\u001b[0m \u001b[43m \u001b[49m\u001b[43mnode_type\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 190\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 192\u001b[0m \u001b[38;5;66;03m# Raise the warning for soon-to-be deprecated library structure\u001b[39;00m\n\u001b[1;32m 193\u001b[0m bad_data_location_messages \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mlist\u001b[39m(\u001b[38;5;28mset\u001b[39m(bad_data_location_messages))\n", - "File \u001b[0;32m~/GitHub_Public/WOMBAT/wombat/windfarm/system/system.py:76\u001b[0m, in \u001b[0;36mSystem.__init__\u001b[0;34m(self, env, repair_manager, t_id, name, subassemblies, system)\u001b[0m\n\u001b[1;32m 73\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m system \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m (\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mturbine\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124msubstation\u001b[39m\u001b[38;5;124m\"\u001b[39m):\n\u001b[1;32m 74\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124msystem\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m must be one of \u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mturbine\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m or \u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124msubstation\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m!\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m---> 76\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_create_subassemblies\u001b[49m\u001b[43m(\u001b[49m\u001b[43msubassemblies\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msystem\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/GitHub_Public/WOMBAT/wombat/windfarm/system/system.py:110\u001b[0m, in \u001b[0;36mSystem._create_subassemblies\u001b[0;34m(self, subassembly_data, system)\u001b[0m\n\u001b[1;32m 108\u001b[0m \u001b[38;5;28;01mcontinue\u001b[39;00m\n\u001b[1;32m 109\u001b[0m name \u001b[38;5;241m=\u001b[39m create_variable_from_string(key)\n\u001b[0;32m--> 110\u001b[0m subassembly \u001b[38;5;241m=\u001b[39m \u001b[43mSubassembly\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43menv\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mname\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdata\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 111\u001b[0m \u001b[38;5;28msetattr\u001b[39m(\u001b[38;5;28mself\u001b[39m, name, subassembly)\n\u001b[1;32m 112\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msubassemblies\u001b[38;5;241m.\u001b[39mappend(\u001b[38;5;28mgetattr\u001b[39m(\u001b[38;5;28mself\u001b[39m, name))\n", - "File \u001b[0;32m~/GitHub_Public/WOMBAT/wombat/windfarm/system/subassembly.py:52\u001b[0m, in \u001b[0;36mSubassembly.__init__\u001b[0;34m(self, system, env, s_id, subassembly_data)\u001b[0m\n\u001b[1;32m 45\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mid \u001b[38;5;241m=\u001b[39m s_id\n\u001b[1;32m 47\u001b[0m subassembly_data \u001b[38;5;241m=\u001b[39m {\n\u001b[1;32m 48\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39msubassembly_data,\n\u001b[1;32m 49\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124msystem_value\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msystem\u001b[38;5;241m.\u001b[39mvalue,\n\u001b[1;32m 50\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrng\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39menv\u001b[38;5;241m.\u001b[39mrandom_generator,\n\u001b[1;32m 51\u001b[0m }\n\u001b[0;32m---> 52\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdata \u001b[38;5;241m=\u001b[39m \u001b[43mSubassemblyData\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfrom_dict\u001b[49m\u001b[43m(\u001b[49m\u001b[43msubassembly_data\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 53\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mname \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdata\u001b[38;5;241m.\u001b[39mname\n\u001b[1;32m 55\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39moperating_level \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m1.0\u001b[39m\n", - "File \u001b[0;32m~/GitHub_Public/WOMBAT/wombat/core/data_classes.py:394\u001b[0m, in \u001b[0;36mFromDictMixin.from_dict\u001b[0;34m(cls, data)\u001b[0m\n\u001b[1;32m 389\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m undefined:\n\u001b[1;32m 390\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mAttributeError\u001b[39;00m(\n\u001b[1;32m 391\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mThe class defintion for \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mcls\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m is missing the following\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 392\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m inputs: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mundefined\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 393\u001b[0m )\n\u001b[0;32m--> 394\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mcls\u001b[39;49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m:9\u001b[0m, in \u001b[0;36m__init__\u001b[0;34m(self, name, maintenance, failures, system_value, rng)\u001b[0m\n\u001b[1;32m 7\u001b[0m _setattr(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mrng\u001b[39m\u001b[38;5;124m'\u001b[39m, rng)\n\u001b[1;32m 8\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m _config\u001b[38;5;241m.\u001b[39m_run_validators \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[0;32m----> 9\u001b[0m \u001b[43m__attr_validator_rng\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m__attr_rng\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrng\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 10\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m__attrs_post_init__()\n", - "File \u001b[0;32m~/miniforge3/envs/wombat-fresh/lib/python3.10/site-packages/attr/validators.py:100\u001b[0m, in \u001b[0;36m_InstanceOfValidator.__call__\u001b[0;34m(self, inst, attr, value)\u001b[0m\n\u001b[1;32m 96\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 97\u001b[0m \u001b[38;5;124;03mWe use a callable class to be able to change the ``__repr__``.\u001b[39;00m\n\u001b[1;32m 98\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 99\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(value, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtype):\n\u001b[0;32m--> 100\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\n\u001b[1;32m 101\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;132;01m{name}\u001b[39;00m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m must be \u001b[39m\u001b[38;5;132;01m{type!r}\u001b[39;00m\u001b[38;5;124m (got \u001b[39m\u001b[38;5;132;01m{value!r}\u001b[39;00m\u001b[38;5;124m that is a \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 102\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{actual!r}\u001b[39;00m\u001b[38;5;124m).\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;241m.\u001b[39mformat(\n\u001b[1;32m 103\u001b[0m name\u001b[38;5;241m=\u001b[39mattr\u001b[38;5;241m.\u001b[39mname,\n\u001b[1;32m 104\u001b[0m \u001b[38;5;28mtype\u001b[39m\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtype,\n\u001b[1;32m 105\u001b[0m actual\u001b[38;5;241m=\u001b[39mvalue\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__class__\u001b[39m,\n\u001b[1;32m 106\u001b[0m value\u001b[38;5;241m=\u001b[39mvalue,\n\u001b[1;32m 107\u001b[0m ),\n\u001b[1;32m 108\u001b[0m attr,\n\u001b[1;32m 109\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtype,\n\u001b[1;32m 110\u001b[0m value,\n\u001b[1;32m 111\u001b[0m )\n", - "\u001b[0;31mTypeError\u001b[0m: (\"'rng' must be (got that is a ).\", Attribute(name='rng', default=NOTHING, validator=>, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type='np.random._generator.Generator', converter=None, kw_only=False, inherited=False, on_setattr=None, alias='rng'), , )" - ] - } - ], - "source": [ - "sim = Simulation.from_config(config)\n", - "\n", - "# Delete any files that get initialized through the simulation environment\n", - "sim.env.cleanup_log_files()" - ] - }, - { - "cell_type": "markdown", - "id": "c7da1b1b", - "metadata": {}, - "source": [ - "### Option 2: `Simulation()`\n", - "\n", - "Load the configuration file automatically given a library path and configuration file name.\n", - "\n", - "In this usage, the string \"DINWOODIE\" can be used because the `Simulation` class knows\n", - "to look for this library mapping, as well as the \"IEA_26\" mapping for the two validation\n", - "cases that we demonstrate in the examples folder.\n", - "\n", - "```{note}\n", - "In Option 2, the config parameter can also be set with a dictionary.\n", - "\n", - "The library path in the configuration file should match the one provided, or the\n", - "setup steps will fail in the simulation.\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "89a03508", - "metadata": {}, - "outputs": [], - "source": [ - "sim = Simulation(\n", - " library_path=\"DINWOODIE\", # automatically directs to the provided library\n", - " config=\"base.yaml\"\n", - ")\n", - "sim.env.cleanup_log_files()" - ] - }, - { - "cell_type": "markdown", - "id": "fc53e90d", - "metadata": {}, - "source": [ - "### Seeding the simulation random variable\n", - "\n", - "Using `random_seed` a simulation can be seeded to produce the same results every single\n", - "time, or a `random_generator` can be provided to use the same generator for a batch of\n", - "identical simulations to better understand the variations in results." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "f0e39a95", - "metadata": {}, - "outputs": [], - "source": [ - "sim = Simulation(\n", - " library_path=\"DINWOODIE\", # automatically directs to the provided library\n", - " config=\"base.yaml\",\n", - " random_seed=2023, # integer value indicating how to seed the internally-created generator\n", - ")\n", - "sim.env.cleanup_log_files()\n", - "\n", - "rng = np.random.default_rng(seed=2023) # create the generator\n", - "sim = Simulation(\n", - " library_path=\"DINWOODIE\", # automatically directs to the provided library\n", - " config=\"base.yaml\",\n", - " random_generator=rng, # generator that can be shared among all processes\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "954be977", - "metadata": {}, - "source": [ - "## Run the analysis\n", - "\n", - "When the run method is called, the default run time is for the full length of the\n", - "simulation, however, if a shorter run than was previously designed is required for\n", - "debugging, or something similar, we can use `sum.run(until=)` to do this. In\n", - "the `run` method, not only is the simulation run, but the metrics class is loaded at the\n", - "end to quickly transition to results aggregation without any further code.\n", - "\n", - "```{warning}\n", - "It should be noted at this stage that a run time that isn't divisible by 8760 (hours in\n", - "a year), the run will fail if the SAM financial model is being used due to a mismatch\n", - "with PySAM's requirements and the model's outputs. This will be worked\n", - "out in later iterations to cap it to the correct number of hours (future feature) for an\n", - "evenly divisible year.\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, so no errors will occur.\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "80ab88c0", - "metadata": {}, - "outputs": [ + "cell_type": "markdown", + "id": "7fc99988", + "metadata": {}, + "source": [ + "# How To Use WOMBAT\n", + "\n", + "This tutorial will walk through the setup, running, and results stages of a WOMBAT\n", + "simulation while providing background information about how each component is related.\n", + "\n", + "## Imports\n", + "\n", + "The following code block demonstrates a typical setup for working with WOMBAT and running\n", + "analyses." + ] + }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "Run time: 0.46 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", - "print(f\"Run time: {timing / 60:,.2f} minutes\")" - ] - }, - { - "cell_type": "markdown", - "id": "fd4c43e2", - "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": 8, - "id": "ec8e0751", - "metadata": {}, - "outputs": [ + "cell_type": "code", + "execution_count": 1, + "id": "71b64545", + "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 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 the legibility of DataFrames\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)" + ] + }, { - "name": "stdout", - "output_type": "stream", - "text": [ - " Net Capacity Factor: 46.2%\n", - "Gross Capacity Factor: 47.7%\n" - ] - } - ], - "source": [ - "net_cf = sim.metrics.capacity_factor(which=\"net\", frequency=\"project\", by=\"windfarm\").values[0][0]\n", - "gross_cf = sim.metrics.capacity_factor(which=\"gross\", frequency=\"project\", by=\"windfarm\").values[0][0]\n", - "print(f\" Net Capacity Factor: {net_cf:2.1%}\")\n", - "print(f\"Gross Capacity Factor: {gross_cf:2.1%}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "a7db5479", - "metadata": {}, - "outputs": [ + "cell_type": "markdown", + "id": "d90c3ca1", + "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\n", + "`library/code_comparison/dinwoodie` in the Github repository, and specifically the base\n", + "case.\n", + "\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", + "```{warning}\n", + "As of v0.6, the following structure will be adopted to mirror the format of the\n", + "[ORBIT library structure](https://github.com/WISDEM/ORBIT/blob/master/ORBIT/core/library.py#L7-L24)\n", + "to increase compatibility between similar libraries.\n", + "\n", + "As of v0.9, the library structure shown below is the only one that will work.\n", + "```\n", + "\n", + "To help users convert to the new structure, the following method is provided to create\n", + "the required folder structure for users.\n", + "\n", + "```{code-block} python\n", + "\n", + "from wombat import create_library_structure # located in wombat.core.library\n", + "\n", + "new_library = \"library\"\n", + "create_library_structure(new_library)\n", + "```\n", + "\n", + "The above method call will produce the below folder and subfolder structure.\n", + "\n", + "```\n", + "\n", + " \u251c\u2500\u2500 project\n", + " \u251c\u2500\u2500 config <- Project-level configuration files\n", + " \u251c\u2500\u2500 port <- Port configuration files\n", + " \u251c\u2500\u2500 plant <- Wind farm layout files\n", + " \u251c\u2500\u2500 cables <- Export and Array cable configuration files\n", + " \u251c\u2500\u2500 substations <- Substation configuration files\n", + " \u251c\u2500\u2500 turbines <- Turbine configuration and power curve files\n", + " \u251c\u2500\u2500 vessels <- Land-based and offshore servicing equipment configuration files\n", + " \u251c\u2500\u2500 weather <- Weather profiles\n", + " \u251c\u2500\u2500 results <- The analysis log files and any saved output data\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", + "In practice, any folder location can be used so long as it follows the subfolder structure provided\n", + "here.\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", + "type (optional)\n", + ": One of \"turbine\" or \"substation\". This is required to accurately model a multi-substation wind farm. The base assumption is that a substation connects to itself as a means to model the export cable connecting to the interconnection point, however, this is not always the case, as substations may be connected through their export systems. Using this filed allows for that connection to be modeled accurately.\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", + "upstream_cable_name (optional)\n", + ": The descriptive name to give to the cable that will be used during logging. This enables users to use a single cable definition file while maintaining the naming conventions used for the wind farm being simulated.\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", + "
\n", + "\n", + "| id | substation_id | name | type | longitude | latitude | string | order | distance | subassembly | upstream_cable |\n", + "| :-- | :-- | :-- | :-- | --: | --: | --: | --: | --: | :-- | :-- |\n", + "| OSS1 | OSS1 | OSS1 | substation | 0 | 0 | | | | offshore_substation.yaml | export.yaml |\n", + "| S00T1 | OSS1 | S00T1 | turbine | 0 | 0 | 0 | 0 | 0 | vestas_v90.yaml | array.yaml |\n", + "| S00T2 | OSS1 | S00T2 | turbine | 0 | 0 | 0 | 1 | 0 | vestas_v90.yaml | array.yaml |\n", + "| S00T3 | OSS1 | S00T3 | turbine | 0 | 0 | 0 | 2 | 0 | vestas_v90.yaml | array.yaml |\n", + "| ... |\n", + "| S00T79 | OSS1 | S00T79 | turbine | 0 | 0 | 0 | 78 | 0 | vestas_v90.yaml | array.yaml |\n", + "| S00T80 | OSS1 | S00T80 | turbine | 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", + "\n", + "datetime (required)\n", + ": A date and time stamp, any format.\n", + "\n", + "windspeed (required)\n", + ": The hourly, mean windspeed, in meters per second at the time stamp.\n", + "\n", + "waveheight (optional)\n", + ": The hourly, mean waveheight, in meters, at 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", + "### Environmental Considerations\n", + "\n", + "In addition to using weather conditions for site characteristics, WOMBAT is able to\n", + "model environmental considerations where a port or site cannot be accessed, for example,\n", + "when silt builds up and water depths become too low to tow a turbine into the port.\n", + "There is also a feature for imposing maximum operating speeds, such as when there are\n", + "animal migrations and vessels must slow down to avoid collisions with endangered species.\n", + "\n", + "Defining the `non_operational_start` and `non_operational_end` at either the servicing\n", + "equipment, environment, or port level allows for the creation of an annualized date\n", + "range spanning the length of the simulation where operations are not allowed to occur.\n", + "When defined at the environment level, all servicing equipment and a port, if defined,\n", + "will have this non-operational period applied, and if it's already existing, the more\n", + "conservative of the date ranges will be applied. When defined at the port level, all\n", + "associated servicing equipment (tugboats) will have the same inuring priority as when\n", + "defined at the environment level.\n", + "\n", + "The same logic applies when defining the `reduced_speed_start` and `reduced_speed_end`\n", + "for defining when the operating speeds of servicing equipment are capped at the\n", + "`reduced_speed`. As is the case above, these variables can also be defined at the\n", + "servicing equipment level for further customization.\n", + "\n", + "\n", + "### Fixed Costs\n", + "\n", + "Please see the [`FixedCosts` API documentation](../API/types.md#fixed-cost-model) for\n", + "details on this optional piece of financial modeling.\n", + "\n", + "For modeling a tow-to-port strategy that the port rental costs should be included in\n", + "this category, and not in the port configuration.\n", + "\n", + "\n", + "### Servicing Equipment\n", + "\n", + "The servicing equipment control the actual repairs within a simulation, and as of v0.5,\n", + "there are four different repair strategies that can be used: scheduled, downtime-based\n", + "unscheduled, repair-based unscheduled, and tow-to-port. These are options are controlled\n", + "through the `capability` settings in each equipment's configuration in conjunction with\n", + "the `service_equipment` setting in the maintenance and failure configurations for each\n", + "subassembly.\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. These codes do not have separate operating models,\n", + "but instead allow the user to specify the types of operations the servicing equipment\n", + "will be able to operate on. This model should be aligned with the `service_equipment`\n", + "requirements in the subassembly failure and maintenance models.\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/onsite truck\n", + "\n", + "SCN\n", + ": small crane (i.e., field support vessel or cherry picker)\n", + "\n", + "LCN\n", + ": large crane (i.e., heavy lift vessel or crawler crane)\n", + "\n", + "CAB\n", + ": cabling-specific vessel/vehicle\n", + "\n", + "DSV\n", + ": diving support vessel\n", + "\n", + "TOW\n", + ": tugboat/towing (a failure with this setting will trigger a tow-to-port scenario where\n", + "the simulation's `Port` will dispatch the tugboats as needed)\n", + "\n", + "AHV\n", + ": anchor handling vessel (this is a variation on the tugboat for mooring repairs that\n", + "will not tow anthing between port and site, but operate at the site)\n", + "\n", + "Aside from the TOW and AHV capabilities there are no operations specific to each\n", + "capability. The remaining configurations of a servicing equipment such as equipment\n", + "rates, mobilization, labor, and operating limits will define the nature of its operation\n", + "in tandem with a failure's `time` field. So for a remote reset (RMT), there will be a\n", + "trivial equipment rate, if any, associated with it to account for the specific operations\n", + "or resetting the subassembly remotely. Similarly, a drone repair or inspection (DRN)\n", + "will not require any onboard crew, so labor will 0, or low if assuming an operator that\n", + "is unaccounted for in the site `FixedCosts`, but will require more time than a remote\n", + "reset in addition to a higher equipment cost.\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 downtime.\n", + "\n", + "\n", + "### The System Models\n", + "\n", + "The actual assets on the windfarm such as cables, turbines, and substations, are\n", + "referred to as systems in WOMBAT, and each has their own individual model. Within each\n", + "of these systems, there are user-defined subassemblies (or componenents) that rely on\n", + "two types of repair models:\n", + "\n", + "- maintenance: scheduled, fixed time interval-based maintenance tasks\n", + "- failures: unscheduled, Weibull distribution-based modeled, maintenance tasks\n", + "\n", + "The subassemblies keys in the system YAML definition can be user-defined\n", + "to accommodate the varying language among industry groups and generations of wind\n", + "technologies. The only restrictions are the YAML keys must not contain any special\n", + "characters, and cannot be repeated\n", + "\n", + "In the example below we show a `generator` subassembly with an annual service task and\n", + "frequent manual reset. For a thorough definition, please read the API\n", + "documentation of the [Maintenance](../API/types.md#maintenance-tasks) and\n", + "[Failure](../API/types.md#failures) data classes. Note that the yaml defintion below\n", + "specifies that maintenance tasks are in a bulleted list format and that failure\n", + "defintions require a dictionary-style input with keys to match the severity level of a\n", + "given failure. For more details on the complete subassembly definition, please visit the\n", + "[Subassembly API documentation](../API/types.md#subassembly-model).\n", + "\n", + "```{code-block} yaml\n", + "generator:\n", + " name: generator # doesn't need to match the subassembly key that becomes System.id\n", + " maintenance:\n", + " - description: annual service\n", + " time: 60\n", + " materials: 18500\n", + " service_equipment: CTV\n", + " frequency: 365\n", + " operation_reduction: 0 # default value\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", + " replacement: False # default value\n", + " level: 1 # Note that the \"level\" value matches the key \"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 all turbines in the windfarm, neglecting any losses. Only needed if\n", + "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", + "Additional keys can be added to represent subassemblies, such as a transformer, in the\n", + "same format as the generator example above. Similarly, a user can define as man or as\n", + "few of the subassemblies as desired with their preferred naming conventions\n", + "\n", + "The following is an example of substation YAML definition with no modeled subasemblies.\n", + "\n", + "```{code-block} yaml\n", + "capacity_kw: 670000\n", + "capex_kw: 140\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 subassembly 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", + "The desired interval, in m/s, between adjacent points on the power curve to be used for\n", + "power calculations.\n", + "\n", + "The `windfarm/vestas_v90.yaml` data file provides the following definition in addition\n", + "to the the maintenance and failure definitions that were shown previously.\n", + "\n", + "```{code-block} yaml\n", + "capacity_kw: 3000\n", + "capex_kw: 1300\n", + "power_curve:\n", + " file: vestas_v90_power_curve.csv\n", + " bin_width: 0.5\n", + "```\n", + "\n", + "The power curve input CSV requires the following two columns: `windspeed_ms` and\n", + "`power_kw` that should be defined using the windspeed for a bin, in m/s and the power produced at that\n", + "windspeed, in kW. The current method available for generating the power curve is the IEC\n", + "61400-12-1-2 method for a wind-speed binned power curve. If there is a need/desire for\n", + "additional power curve methodologies, then [please submit an issue on the GitHub](https://github.com/WISDEM/WOMBAT/issues)!\n", + "\n", + "For an open source listing of a variety of land-based, offshore, and distributed wind\n", + "turbine power curves, please visit the\n", + "[NREL Turbine Models repository](https://github.com/NREL/turbine-models).\n", + "\n", + "\n", + "#### Cables\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. It should be noted that the scheme\n", + "is a combination of both the system and subassembly configuration.\n", + "\n", + "For export cables that connect substations, as opposed to an interconnection, they will\n", + "not create dependencies because it is assumed they bypass the substation altogether,\n", + "and connect directly with the export system. This means that if there is a failure at a\n", + "downstream substation, the connecting export cable and its upstream turbine, substation,\n", + "and cable connections will continue to operate normally.\n", + "\n", + "```{code-block} yaml\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", + "\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.\n", + "\n", + "```{note}\n", + "If a custom library is being used, the `library_path` must be the full path name to the\n", + "location of the folder where the configuration data is contained.\n", + "```\n", + "\n", + "```{warning}\n", + "In v0.6, a new library structure\n", + "```" + ] + }, { - "name": "stdout", - "output_type": "stream", - "text": [ - " Project time-based availability: 96.8%\n" - ] + "cell_type": "code", + "execution_count": 2, + "id": "1c4243b4", + "metadata": {}, + "outputs": [], + "source": [ + "library_path = DINWOODIE # or user-defined path for an external data library" + ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "Project energy-based availability: 96.9%\n", - " Project equipment costs: $433,521.02/MW\n" - ] + "cell_type": "markdown", + "id": "79c118a1", + "metadata": {}, + "source": [ + "### The configuration file\n", + "\n", + "\n", + "In the configuration below, there are a number of data points that will define our\n", + "windfarm layout, weather conditions, working hours, customized start and completion\n", + "years, project size, financials, and the servicing equipment to be used. Note that there\n", + "can be as many or as few of the servicing equipment units as desired.\n", + "\n", + "The purpose of an overarching configuration file is to provide a single place to define\n", + "the primary inputs for a simulation. Below the base configuration is loaded and displayed\n", + "with comments to show where each of files are located in the library structure. WOMBAT\n", + "will know where to go for these pointers when the simulation is initialized so the data\n", + "is constructed and validated correctly." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "d1d8ca1f", + "metadata": {}, + "outputs": [], + "source": [ + "config = load_yaml(library_path / \"project/config\", \"base.yaml\")" + ] + }, + { + "cell_type": "markdown", + "id": "b44b7ebf", + "metadata": {}, + "source": [ + "```{code-block} yaml\n", + "# Contents of: dinwoodie / config / base.yaml\n", + "name: dinwoodie_base\n", + "weather: alpha_ventus_weather_2002_2014.csv # located in: dinwoodie / weather\n", + "service_equipment:\n", + "# YAML-encoded list, but could also be created in standard Python list notation with\n", + "# square brackets: [ctv1.yaml, ctv2.yaml, ..., hlv_requests.yaml]\n", + "# All below equipment configurations are located in: dinwoodie / vessels\n", + " - ctv1.yaml\n", + " - ctv2.yaml\n", + " - ctv3.yaml\n", + " - fsv_requests.yaml\n", + " - hlv_requests.yaml\n", + "layout: layout.csv # located in: dinwoodie / windfarm\n", + "inflation_rate: 0\n", + "fixed_costs: fixed_costs.yaml # located in: dinwoodie / project / config\n", + "workday_start: 7\n", + "workday_end: 19\n", + "start_year: 2003\n", + "end_year: 2012\n", + "project_capacity: 240\n", + "# port: base_port.yaml <- When running a tow-to-port simulation the port configuration\n", + "# pointer is provided here and located in: dinwoodie / project / port\n", + "```\n", + "\n", + "## Create a simulation\n", + "\n", + "There are two ways that this could be done, the first is to use the classmethod\n", + "`Simulation.from_config()`, which allows for the full path string, a dictionary, or\n", + "`Configuration` object to passed as an input, and the second is through a standard\n", + "class initialization.\n", + "\n", + "### Option 1: `Simulation.from_config()`\n", + "\n", + "Load the file from the `Configuration` object that was created in the prior code black" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "206941f8", + "metadata": {}, + "outputs": [], + "source": [ + "sim = Simulation.from_config(library_path=library_path, config=config)\n", + "\n", + "# Delete any files that get initialized through the simulation environment\n", + "sim.env.cleanup_log_files()" + ] + }, + { + "cell_type": "markdown", + "id": "ded5b413", + "metadata": {}, + "source": [ + "### Option 2: `Simulation()`\n", + "\n", + "Load the configuration file automatically given a library path and configuration file name.\n", + "\n", + "In this usage, the string \"DINWOODIE\" can be used because the `Simulation` class knows\n", + "to look for this library mapping, as well as the \"IEA_26\" mapping for the two validation\n", + "cases that we demonstrate in the examples folder.\n", + "\n", + "```{note}\n", + "In Option 2, the config parameter can also be set with a dictionary.\n", + "\n", + "The library path in the configuration file should match the one provided, or the\n", + "setup steps will fail in the simulation.\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d3bf734e", + "metadata": {}, + "outputs": [], + "source": [ + "sim = Simulation(\n", + " library_path=\"DINWOODIE\", # automatically directs to the provided library\n", + " config=\"base.yaml\"\n", + ")\n", + "sim.env.cleanup_log_files()" + ] + }, + { + "cell_type": "markdown", + "id": "1349aa73", + "metadata": {}, + "source": [ + "### Seeding the simulation random variable\n", + "\n", + "Using `random_seed` a simulation can be seeded to produce the same results every single\n", + "time, or a `random_generator` can be provided to use the same generator for a batch of\n", + "identical simulations to better understand the variations in results." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "4b8c14ba", + "metadata": {}, + "outputs": [], + "source": [ + "sim = Simulation(\n", + " library_path=\"DINWOODIE\", # automatically directs to the provided library\n", + " config=\"base.yaml\",\n", + " random_seed=2023, # integer value indicating how to seed the internally-created generator\n", + ")\n", + "sim.env.cleanup_log_files()\n", + "\n", + "rng = np.random.default_rng(seed=2023) # create the generator\n", + "sim = Simulation(\n", + " library_path=\"DINWOODIE\", # automatically directs to the provided library\n", + " config=\"base.yaml\",\n", + " random_generator=rng, # generator that can be shared among all processes\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "31039992", + "metadata": {}, + "source": [ + "## Run the analysis\n", + "\n", + "When the run method is called, the default run time is for the full length of the\n", + "simulation, however, if a shorter run than was previously designed is required for\n", + "debugging, or something similar, we can use `sum.run(until=)` to do this. In\n", + "the `run` method, not only is the simulation run, but the metrics class is loaded at the\n", + "end to quickly transition to results aggregation without any further code." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "50d2ff1a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Run time: 0.50 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", + "print(f\"Run time: {timing / 60:,.2f} minutes\")" + ] + }, + { + "cell_type": "markdown", + "id": "6c6f6cb1", + "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": 8, + "id": "2c55db0a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Net Capacity Factor: 46.0%\n", + "Gross Capacity Factor: 47.7%\n" + ] + } + ], + "source": [ + "net_cf = sim.metrics.capacity_factor(which=\"net\", frequency=\"project\", by=\"windfarm\").values[0][0]\n", + "gross_cf = sim.metrics.capacity_factor(which=\"gross\", frequency=\"project\", by=\"windfarm\").values[0][0]\n", + "print(f\" Net Capacity Factor: {net_cf:2.1%}\")\n", + "print(f\"Gross Capacity Factor: {gross_cf:2.1%}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "7a327086", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Project time-based availability: 100.0%\n", + "Project energy-based availability: 96.5%\n", + " Project equipment costs: $447,712.09/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.windfarm[0]:.1%}\")\n", + "\n", + "total = sim.metrics.production_based_availability(frequency=\"project\", by=\"windfarm\")\n", + "print(f\"Project energy-based availability: {total.windfarm[0]:.1%}\")\n", + "\n", + "total = sim.metrics.equipment_costs(frequency=\"project\", by_equipment=False)\n", + "print(f\" Project equipment costs: ${total.values[0][0] / sim.metrics.project_capacity:,.2f}/MW\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "2ca5e1b6", + "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": 10, + "id": "adf7fe24", + "metadata": {}, + "outputs": [], + "source": [ + "sim.env.cleanup_log_files()" + ] } - ], - "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.windfarm[0]:.1%}\")\n", - "\n", - "total = sim.metrics.production_based_availability(frequency=\"project\", by=\"windfarm\")\n", - "print(f\"Project energy-based availability: {total.windfarm[0]:.1%}\")\n", - "\n", - "total = sim.metrics.equipment_costs(frequency=\"project\", by_equipment=False)\n", - "print(f\" Project equipment costs: ${total.values[0][0] / sim.metrics.project_capacity:,.2f}/MW\")\n" - ] - }, - { - "cell_type": "markdown", - "id": "5fa6a813", - "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": 10, - "id": "276b4233", - "metadata": {}, - "outputs": [], - "source": [ - "sim.env.cleanup_log_files()" - ] - } - ], - "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.10.12" + ], + "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.10.9" + }, + "source_map": [ + 10, + 22, + 38, + 481, + 483, + 499, + 501, + 539, + 545, + 562, + 568, + 576, + 590, + 600, + 610, + 618, + 625, + 636, + 643 + ] }, - "source_map": [ - 10, - 22, - 38, - 513, - 515, - 531, - 533, - 572, - 578, - 595, - 601, - 609, - 623, - 646, - 656, - 664, - 671, - 682, - 689 - ] - }, - "nbformat": 4, - "nbformat_minor": 5 + "nbformat": 4, + "nbformat_minor": 5 } diff --git a/docs/source/examples/how_to.md b/docs/source/examples/how_to.md index 2bfd3bd6..4eabceda 100644 --- a/docs/source/examples/how_to.md +++ b/docs/source/examples/how_to.md @@ -503,7 +503,6 @@ config = load_yaml(library_path / "project/config", "base.yaml") ```{code-block} yaml # Contents of: dinwoodie / config / base.yaml name: dinwoodie_base -library: DINWOODIE weather: alpha_ventus_weather_2002_2014.csv # located in: dinwoodie / weather service_equipment: # YAML-encoded list, but could also be created in standard Python list notation with @@ -539,7 +538,7 @@ Load the file from the `Configuration` object that was created in the prior code ```{code-cell} ipython3 -sim = Simulation.from_config(library_path, config) +sim = Simulation.from_config(library_path=library_path, config=config) # Delete any files that get initialized through the simulation environment sim.env.cleanup_log_files() @@ -590,7 +589,6 @@ sim = Simulation( ) ``` - ## Run the analysis When the run method is called, the default run time is for the full length of the diff --git a/wombat/core/simulation_api.py b/wombat/core/simulation_api.py index 2b77f7be..4f388281 100644 --- a/wombat/core/simulation_api.py +++ b/wombat/core/simulation_api.py @@ -215,7 +215,7 @@ def _create_configuration( @classmethod def from_config( - cls, library: str | Path, config: str | Path | dict | Configuration + cls, library_path: str | Path, config: str | Path | dict | Configuration ): """Creates the ``Simulation`` object only the configuration contents as either a full file path to the configuration file, a dictionary of the configuration @@ -223,6 +223,10 @@ def from_config( Parameters ---------- + library_path : str | Path + The simulation's data library. If a filename is provided for + :py:attr:`config`, this is the data library from where it will be imported. + This will also be used to feed into the returned `Simulation.library_path`. config : str | Path | dict | Configuration The simulation configuration, see ``Configuration`` for more details on the contents. The following is a description of the acceptable contents: @@ -241,9 +245,9 @@ def from_config( Simulation A ready-to-run ``Simulation`` object. """ - library = _library_mapper(library) + library_path = _library_mapper(library_path) if isinstance(config, (str, Path)): - config = library / "project" / "config" / config + config = library_path / "project" / "config" / config config = load_yaml(config.parent, config.name) if isinstance(config, dict): config = Configuration.from_dict(config) @@ -254,7 +258,7 @@ def from_config( if TYPE_CHECKING: assert isinstance(config, Configuration) # mypy helper return cls( # type: ignore - library_path=library, + library_path=library_path, config=config, random_seed=config.random_seed, random_generator=config.random_generator,