Skip to content

Geometry Module

API reference for the geometry module.

geometry

Module for handling geometry data from OWI metadatabase.

Classes

GeometryAPI

GeometryAPI(api_subdir='/geometry/userroutes/', **kwargs)

Bases: API

Class to connect to the geometry data API with methods to retrieve data.

Create an instance of the GeometryAPI class with required parameters.

Parameters:

Name Type Description Default
api_subdir str

Subdirectory of the API endpoint url for specific type of data.

'/geometry/userroutes/'
**kwargs

Additional parameters to pass to the API (see the base class).

{}

Examples:

>>> api = GeometryAPI(
...     api_root="https://example",
...     header={"Authorization": "Token test"},
... )
>>> api.api_root.endswith("/geometry/userroutes/")
True
Source code in src/owi/metadatabase/geometry/io.py
def __init__(
    self,
    api_subdir: str = "/geometry/userroutes/",
    **kwargs,
) -> None:
    """
    Create an instance of the GeometryAPI class with required
    parameters.

    Parameters
    ----------
    api_subdir : str, optional
        Subdirectory of the API endpoint url for specific type of
        data.
    **kwargs
        Additional parameters to pass to the API (see the base
        class).

    Examples
    --------
    >>> api = GeometryAPI(
    ...     api_root="https://example",
    ...     header={"Authorization": "Token test"},
    ... )
    >>> api.api_root.endswith("/geometry/userroutes/")
    True
    """
    super().__init__(**kwargs)
    self.loc_api = LocationsAPI(**kwargs)
    self.api_root = self.api_root + api_subdir
Functions
__eq__
__eq__(other)

Compare two instances of the API class.

Parameters:

Name Type Description Default
other object

Another instance of the API class or a dictionary.

required

Returns:

Type Description
bool

True if the instances are equal, False otherwise.

Raises:

Type Description
AssertionError

If comparison is not possible due to incompatible types.

Examples:

>>> api_1 = API(api_root="https://example", token="test")
>>> api_2 = API(api_root="https://example", token="test")
>>> api_1 == api_2
True
Source code in src/owi/metadatabase/io.py
def __eq__(self, other: object) -> bool:
    """
    Compare two instances of the API class.

    Parameters
    ----------
    other : object
        Another instance of the API class or a dictionary.

    Returns
    -------
    bool
        True if the instances are equal, False otherwise.

    Raises
    ------
    AssertionError
        If comparison is not possible due to incompatible types.

    Examples
    --------
    >>> api_1 = API(api_root="https://example", token="test")
    >>> api_2 = API(api_root="https://example", token="test")
    >>> api_1 == api_2
    True
    """
    if not isinstance(other, (API, dict)):
        return NotImplemented
    if isinstance(other, type(self)):
        comp = deepcompare(self, other)
        assert comp[0], comp[1]
    elif isinstance(other, dict):
        comp = deepcompare(self.__dict__, other)
        assert comp[0], comp[1]
    else:
        raise AssertionError("Comparison is not possible due to incompatible types!")
    return comp[0]
send_request
send_request(url_data_type, url_params)

Handle sending appropriate request.

Handles sending appropriate request according to the type of authentication.

Parameters:

Name Type Description Default
url_data_type str

Type of the data we want to request (according to database model).

required
url_params Mapping

Parameters to send with the request to the database.

required

Returns:

Type Description
Response

An instance of the Response object.

Raises:

Type Description
InvalidParameterError

If neither header nor username and password are defined.

Examples:

>>> from types import SimpleNamespace
>>> from unittest import mock
>>> response = SimpleNamespace(status_code=200, reason="OK")
>>> with mock.patch("requests.get", return_value=response):
...     api = API(api_root="https://example", token="test")
...     resp = api.send_request("/projects", {})
>>> resp is response
True
Source code in src/owi/metadatabase/io.py
def send_request(
    self,
    url_data_type: str,
    url_params: Mapping[str, Union[str, float, int, Sequence[Union[str, float, int]], None]],
) -> requests.Response:
    """
    Handle sending appropriate request.

    Handles sending appropriate request according to the type of
    authentication.

    Parameters
    ----------
    url_data_type : str
        Type of the data we want to request (according to database
        model).
    url_params : Mapping
        Parameters to send with the request to the database.

    Returns
    -------
    requests.Response
        An instance of the Response object.

    Raises
    ------
    InvalidParameterError
        If neither header nor username and password are defined.

    Examples
    --------
    >>> from types import SimpleNamespace
    >>> from unittest import mock
    >>> response = SimpleNamespace(status_code=200, reason="OK")
    >>> with mock.patch("requests.get", return_value=response):
    ...     api = API(api_root="https://example", token="test")
    ...     resp = api.send_request("/projects", {})
    >>> resp is response
    True
    """
    if self.header is not None:
        response = requests.get(
            url=self.api_root + url_data_type,
            headers=self.header,
            params=url_params,
        )
    else:
        if self.uname is None or self.password is None:
            e = "Either self.header or self.uname and self.password must be defined."
            raise InvalidParameterError(e)
        else:
            response = requests.get(
                url=self.api_root + url_data_type,
                auth=self.auth,
                params=url_params,
            )
    return response
check_request_health staticmethod
check_request_health(resp)

Check status code of the response and provide details.

Checks status code of the response to request and provides details if unexpected.

Parameters:

Name Type Description Default
resp Response

Instance of the Response object.

required

Raises:

Type Description
APIConnectionError

If response status code is not 200.

Examples:

>>> from types import SimpleNamespace
>>> ok = SimpleNamespace(status_code=200, reason="OK")
>>> API.check_request_health(ok)
Source code in src/owi/metadatabase/io.py
@staticmethod
def check_request_health(resp: requests.Response) -> None:
    """
    Check status code of the response and provide details.

    Checks status code of the response to request and provides
    details if unexpected.

    Parameters
    ----------
    resp : requests.Response
        Instance of the Response object.

    Raises
    ------
    APIConnectionError
        If response status code is not 200.

    Examples
    --------
    >>> from types import SimpleNamespace
    >>> ok = SimpleNamespace(status_code=200, reason="OK")
    >>> API.check_request_health(ok)
    """
    if resp.status_code != 200:
        message = f"Error {resp.status_code}.\n{resp.reason}\n{resp.text}"
        raise APIConnectionError(message=message, response=resp)
output_to_df staticmethod
output_to_df(response)

Transform output to Pandas dataframe.

Parameters:

Name Type Description Default
response Response

Raw output of the sent request.

required

Returns:

Type Description
DataFrame

Pandas dataframe of the data from the output.

Raises:

Type Description
DataProcessingError

If failed to decode JSON from API response.

Examples:

>>> from types import SimpleNamespace
>>> resp = SimpleNamespace(text='[{"a": 1}]')
>>> int(API.output_to_df(resp)["a"].iloc[0])
1
Source code in src/owi/metadatabase/io.py
@staticmethod
def output_to_df(response: requests.Response) -> pd.DataFrame:
    """
    Transform output to Pandas dataframe.

    Parameters
    ----------
    response : requests.Response
        Raw output of the sent request.

    Returns
    -------
    pd.DataFrame
        Pandas dataframe of the data from the output.

    Raises
    ------
    DataProcessingError
        If failed to decode JSON from API response.

    Examples
    --------
    >>> from types import SimpleNamespace
    >>> resp = SimpleNamespace(text='[{"a": 1}]')
    >>> int(API.output_to_df(resp)["a"].iloc[0])
    1
    """
    try:
        data = json.loads(response.text)
    except Exception as err:
        raise DataProcessingError("Failed to decode JSON from API response") from err
    return pd.DataFrame(data)
postprocess_data staticmethod
postprocess_data(df, output_type)

Process dataframe information to extract additional data.

Processes dataframe information to extract the necessary additional data.

Parameters:

Name Type Description Default
df DataFrame

Dataframe of the output data.

required
output_type str

Expected type (amount) of the data extracted.

required

Returns:

Type Description
PostprocessData

Dictionary of the additional data extracted.

Raises:

Type Description
InvalidParameterError

If more than one record was returned for 'single' output type, or if output type is not 'single' or 'list'.

Examples:

>>> df = pd.DataFrame({"id": [1]})
>>> int(API.postprocess_data(df, "single")["id"])
1
Source code in src/owi/metadatabase/io.py
@staticmethod
def postprocess_data(df: pd.DataFrame, output_type: str) -> PostprocessData:
    """
    Process dataframe information to extract additional data.

    Processes dataframe information to extract the necessary
    additional data.

    Parameters
    ----------
    df : pd.DataFrame
        Dataframe of the output data.
    output_type : str
        Expected type (amount) of the data extracted.

    Returns
    -------
    PostprocessData
        Dictionary of the additional data extracted.

    Raises
    ------
    InvalidParameterError
        If more than one record was returned for 'single' output
        type, or if output type is not 'single' or 'list'.

    Examples
    --------
    >>> df = pd.DataFrame({"id": [1]})
    >>> int(API.postprocess_data(df, "single")["id"])
    1
    """
    if output_type == "single":
        if df.__len__() == 0:
            exists = False
            project_id = None
        elif df.__len__() == 1:
            exists = True
            project_id = df["id"].iloc[0]
        else:
            raise InvalidParameterError("More than one project site was returned, check search criteria.")
        data_add: PostprocessData = {
            "existance": exists,
            "id": project_id,
            "response": None,
        }
    elif output_type == "list":
        exists = df.__len__() != 0
        data_add: PostprocessData = {
            "existance": exists,
            "id": None,
            "response": None,
        }
    else:
        raise InvalidParameterError("Output type must be either 'single' or 'list', not " + output_type + ".")
    return data_add
validate_data staticmethod
validate_data(df, data_type)

Validate the data extracted from the database.

Parameters:

Name Type Description Default
df DataFrame

Dataframe of the output data.

required
data_type str

Type of the data we want to request (according to database model).

required

Returns:

Type Description
DataFrame

Dataframe with corrected data.

Examples:

>>> df = pd.DataFrame()
>>> API.validate_data(df, "subassemblies").empty
True
Source code in src/owi/metadatabase/io.py
@staticmethod
def validate_data(df: pd.DataFrame, data_type: str) -> pd.DataFrame:
    """
    Validate the data extracted from the database.

    Parameters
    ----------
    df : pd.DataFrame
        Dataframe of the output data.
    data_type : str
        Type of the data we want to request (according to database
        model).

    Returns
    -------
    pd.DataFrame
        Dataframe with corrected data.

    Examples
    --------
    >>> df = pd.DataFrame()
    >>> API.validate_data(df, "subassemblies").empty
    True
    """
    z_sa_mp = {"min": -100000, "max": -10000}
    z_sa_tp = {"min": -20000, "max": -1000}
    z_sa_tw = {"min": 1000, "max": 100000}
    sa_type = ["TW", "TP", "MP"]
    z = [z_sa_tw, z_sa_tp, z_sa_mp]
    if data_type == "subassemblies":
        if df.__len__() == 0:
            return df
        for i, sat in enumerate(sa_type):
            cond_small_units = (df["subassembly_type"] == sat) & (df["z_position"] < z[i]["min"])
            cond_big_units = (df["subassembly_type"] == sat) & (df["z_position"] > z[i]["max"])
            if df[cond_small_units].__len__() > 0:
                df.loc[cond_small_units, "z_position"] = df.loc[cond_small_units, "z_position"] / 1e3
                warnings.warn(
                    f"The value of z location for {df.loc[cond_small_units | cond_big_units, 'title'].values} \
                    might be wrong or in wrong units! There will be an attempt to correct the units.",
                    stacklevel=2,
                )
            if df[cond_big_units].__len__() > 0:
                df.loc[cond_big_units, "z_position"] = df.loc[cond_big_units, "z_position"] * 1e3
                warnings.warn(
                    f"The value of z location for {df.loc[cond_small_units | cond_big_units, 'title'].values} \
                    might be wrong or in wrong units! There will be an attempt to correct the units.",
                    stacklevel=2,
                )
    return df
process_data
process_data(url_data_type, url_params, output_type)

Process output data according to specified request parameters.

Parameters:

Name Type Description Default
url_data_type str

Type of the data we want to request (according to database model).

required
url_params Mapping

Parameters to send with the request to the database.

required
output_type str

Expected type (amount) of the data extracted.

required

Returns:

Type Description
tuple

A tuple of dataframe with the requested data and additional data from postprocessing.

Examples:

>>> from types import SimpleNamespace
>>> from unittest import mock
>>> response = SimpleNamespace(text="[]", status_code=200, reason="OK")
>>> api = API(api_root="https://example", token="test")
>>> with mock.patch.object(API, "send_request", return_value=response):
...     df, info = api.process_data("projects", {}, "list")
>>> df.empty
True
>>> info["existance"]
False
Source code in src/owi/metadatabase/io.py
def process_data(
    self,
    url_data_type: str,
    url_params: Mapping[str, Union[str, float, int, Sequence[Union[str, float, int]], None]],
    output_type: str,
) -> tuple[pd.DataFrame, PostprocessData]:
    """
    Process output data according to specified request parameters.

    Parameters
    ----------
    url_data_type : str
        Type of the data we want to request (according to database
        model).
    url_params : Mapping
        Parameters to send with the request to the database.
    output_type : str
        Expected type (amount) of the data extracted.

    Returns
    -------
    tuple
        A tuple of dataframe with the requested data and
        additional data from postprocessing.

    Examples
    --------
    >>> from types import SimpleNamespace
    >>> from unittest import mock
    >>> response = SimpleNamespace(text="[]", status_code=200, reason="OK")
    >>> api = API(api_root="https://example", token="test")
    >>> with mock.patch.object(API, "send_request", return_value=response):
    ...     df, info = api.process_data("projects", {}, "list")
    >>> df.empty
    True
    >>> info["existance"]
    False
    """
    resp = self.send_request(url_data_type, url_params)
    self.check_request_health(resp)
    df = self.output_to_df(resp)
    df = self.validate_data(df, url_data_type)
    df_add = self.postprocess_data(df, output_type)
    # Add the response object to the returned dictionary so tests can inspect it
    df_add["response"] = resp
    return df, df_add
get_model_definitions
get_model_definitions(projectsite=None)

Get all relevant model definitions.

Parameters:

Name Type Description Default
projectsite str

Title of the projectsite.

None

Returns:

Type Description
dict

Dictionary with the following keys:

  • "data": Pandas dataframe with the model definitions
  • "exists": Boolean indicating whether matching records are found

Examples:

    >>> from unittest import mock
    >>> api = GeometryAPI(
    ...     api_root="https://example",
    ...     header={"Authorization": "Token test"},
    ... )
    >>> df = pd.DataFrame({"id": [1]})
    >>> with mock.patch.object(
    ...     GeometryAPI,
    ...     "process_data",
    ...     return_value=(df, {"existance": True}),
    ... ):
    ...     out = api.get_model_definitions(projectsite="Site")
    >>> out["exists"]
    True
Source code in src/owi/metadatabase/geometry/io.py
def get_model_definitions(
    self,
    projectsite: Union[str, None] = None,
) -> dict[str, Union[pd.DataFrame, bool, np.int64, None]]:
    """
    Get all relevant model definitions.

    Parameters
    ----------
    projectsite : str, optional
        Title of the projectsite.

    Returns
    -------
    dict
        Dictionary with the following keys:

        - "data": Pandas dataframe with the model definitions
        - "exists": Boolean indicating whether matching records
          are found

    Examples
    --------
            >>> from unittest import mock
            >>> api = GeometryAPI(
            ...     api_root="https://example",
            ...     header={"Authorization": "Token test"},
            ... )
            >>> df = pd.DataFrame({"id": [1]})
            >>> with mock.patch.object(
            ...     GeometryAPI,
            ...     "process_data",
            ...     return_value=(df, {"existance": True}),
            ... ):
            ...     out = api.get_model_definitions(projectsite="Site")
            >>> out["exists"]
            True
    """
    url_params = {}
    if projectsite is not None:
        url_params["site"] = projectsite
    url_data_type = "modeldefinitions"
    output_type = "list"
    with self._temp_api_root(self.api_root.replace("userroutes", "routes")):
        df, df_add = self.process_data(url_data_type, url_params, output_type)
    return {"data": df, "exists": df_add["existance"]}
get_modeldefinition_id
get_modeldefinition_id(
    assetlocation=None,
    projectsite=None,
    model_definition=None,
)

Get the ID of a model definition.

Either the asset location or the project site must be specified.

Parameters:

Name Type Description Default
assetlocation str

Title of the asset location.

None
projectsite str

Title of the projectsite.

None
model_definition str

Title of the model definition.

None

Returns:

Type Description
dict

Dictionary with the following keys:

  • "id": ID of the specified model definition
  • "multiple_modeldef": Boolean indicating whether there are multiple model definitions for the asset location in general

Raises:

Type Description
ValueError

If at least one of assetlocation or projectsite is not specified, if no location found, if no model definitions found, if multiple model definitions found without specification, or if specified model definition not found.

Examples:

>>> api = GeometryAPI(
...     api_root="https://example",
...     header={"Authorization": "Token test"},
... )
>>> api.get_modeldefinition_id()
Traceback (most recent call last):
    ...
ValueError: At least either of the related ... must be specified!
Source code in src/owi/metadatabase/geometry/io.py
def get_modeldefinition_id(
    self,
    assetlocation: Union[str, None] = None,
    projectsite: Union[str, None] = None,
    model_definition: Union[str, None] = None,
) -> dict[str, Union[int, np.int64, bool, None]]:
    """
    Get the ID of a model definition.

    Either the asset location or the project site must be specified.

    Parameters
    ----------
    assetlocation : str, optional
        Title of the asset location.
    projectsite : str, optional
        Title of the projectsite.
    model_definition : str, optional
        Title of the model definition.

    Returns
    -------
    dict
        Dictionary with the following keys:

        - "id": ID of the specified model definition
        - "multiple_modeldef": Boolean indicating whether there
          are multiple model definitions for the asset location
          in general

    Raises
    ------
    ValueError
        If at least one of assetlocation or projectsite is not
        specified, if no location found, if no model definitions
        found, if multiple model definitions found without
        specification, or if specified model definition not found.

    Examples
    --------
    >>> api = GeometryAPI(
    ...     api_root="https://example",
    ...     header={"Authorization": "Token test"},
    ... )
    >>> api.get_modeldefinition_id()  # doctest: +ELLIPSIS
    Traceback (most recent call last):
        ...
    ValueError: At least either of the related ... must be specified!
    """
    if assetlocation is None and projectsite is None:
        raise ValueError("At least either of the related `assetlocation` or `projectsite` must be specified!")

    result = {}
    if projectsite is None:
        if assetlocation is None:
            raise ValueError("Asset location must be specified when projectsite is None.")
        location_data = self.loc_api.get_assetlocation_detail(assetlocation=assetlocation)
        if location_data["exists"]:
            location = cast(pd.DataFrame, location_data["data"])
        else:
            raise ValueError(f"No location found for asset {assetlocation}.")
        projectsite = location["projectsite_name"].loc[0]
    model_definitions_data = self.get_model_definitions(projectsite=projectsite)
    if model_definitions_data["exists"]:
        model_definitions = cast(pd.DataFrame, model_definitions_data["data"])
    else:
        raise ValueError(f"No model definitions found for project site {projectsite}.")
    if model_definition is None and len(model_definitions) > 1:
        raise ValueError(
            f"Multiple model definitions found for project site {projectsite}. Please specify which one to use."
        )
    if model_definition is None:
        model_definition_id = model_definitions["id"].values[0]
        result["id"] = model_definition_id
        result["multiple_modeldef"] = False
    else:
        matching_definitions = model_definitions[model_definitions["title"] == model_definition]
        if matching_definitions.empty:
            raise ValueError(f"Model definition '{model_definition}' not found for project site {projectsite}.")
        if len(matching_definitions) > 1:
            raise ValueError(
                f"Multiple model definitions found for '{model_definition}' in project site {projectsite}.\n"
                f"Please check the data consistency."
            )
        model_definition_id = matching_definitions["id"].values[0]
        result["id"] = model_definition_id
        result["multiple_modeldef"] = len(model_definitions) > 1
    return result
get_subassemblies
get_subassemblies(
    projectsite=None,
    assetlocation=None,
    subassembly_type=None,
    model_definition=None,
)

Get all relevant structure subassemblies.

If you specify a model definition, you also must specify either the projectsite or the asset location.

Parameters:

Name Type Description Default
projectsite str

Title of the projectsite.

None
assetlocation str

Title of the asset location.

None
subassembly_type str

Type of the subassembly.

None
model_definition str

Title of the model definition.

None

Returns:

Type Description
dict

Dictionary with the following keys:

  • "data": Pandas dataframe with the location data for each project
  • "exists": Boolean indicating whether matching records are found

Raises:

Type Description
ValueError

If model definition specified without projectsite or assetlocation, or if specified model definition not found.

Examples:

>>> from unittest import mock
>>> api = GeometryAPI(
...     api_root="https://example",
...     header={"Authorization": "Token test"},
... )
>>> df = pd.DataFrame({"id": [1]})
>>> with mock.patch.object(
...     GeometryAPI,
...     "process_data",
...     return_value=(df, {"existance": True}),
... ):
...     out = api.get_subassemblies(projectsite="Site")
>>> out["exists"]
True
Source code in src/owi/metadatabase/geometry/io.py
def get_subassemblies(
    self,
    projectsite: Union[str, None] = None,
    assetlocation: Union[str, None] = None,
    subassembly_type: Union[str, None] = None,
    model_definition: Union[str, None] = None,
) -> dict[str, Union[pd.DataFrame, bool, np.int64, None]]:
    """
    Get all relevant structure subassemblies.

    If you specify a model definition, you also must specify either
    the projectsite or the asset location.

    Parameters
    ----------
    projectsite : str, optional
        Title of the projectsite.
    assetlocation : str, optional
        Title of the asset location.
    subassembly_type : str, optional
        Type of the subassembly.
    model_definition : str, optional
        Title of the model definition.

    Returns
    -------
    dict
        Dictionary with the following keys:

        - "data": Pandas dataframe with the location data for each
          project
        - "exists": Boolean indicating whether matching records
          are found

    Raises
    ------
    ValueError
        If model definition specified without projectsite or
        assetlocation, or if specified model definition not found.

    Examples
    --------
    >>> from unittest import mock
    >>> api = GeometryAPI(
    ...     api_root="https://example",
    ...     header={"Authorization": "Token test"},
    ... )
    >>> df = pd.DataFrame({"id": [1]})
    >>> with mock.patch.object(
    ...     GeometryAPI,
    ...     "process_data",
    ...     return_value=(df, {"existance": True}),
    ... ):
    ...     out = api.get_subassemblies(projectsite="Site")
    >>> out["exists"]
    True
    """
    url_params = {}
    func_args = {}
    if projectsite is not None:
        url_params["asset__projectsite__title"] = projectsite
        func_args["projectsite"] = projectsite
    if assetlocation is not None:
        url_params["asset__title"] = assetlocation
        func_args["assetlocation"] = assetlocation
    if subassembly_type is not None:
        url_params["subassembly_type"] = subassembly_type
    if model_definition is not None:
        if projectsite is not None or assetlocation is not None:
            func_args["model_definition"] = model_definition
            modeldef_data = self.get_modeldefinition_id(**func_args)
            if modeldef_data["id"] is not None:
                url_params["model_definition"] = str(modeldef_data["id"])
            else:
                raise ValueError(
                    f"No model definition {model_definition} found for project site {projectsite} "
                    f"or asset location {assetlocation}."
                )
        else:
            raise ValueError(
                "If you specify a model definition, you also must specify either "
                "the projectsite or the asset location!"
            )
    url_data_type = "subassemblies"
    output_type = "list"
    df, df_add = self.process_data(url_data_type, url_params, output_type)
    return {"data": df, "exists": df_add["existance"]}
get_buildingblocks
get_buildingblocks(
    projectsite=None,
    assetlocation=None,
    subassembly_type=None,
    subassembly_id=None,
)

Get all relevant building blocks.

Parameters:

Name Type Description Default
projectsite str

Title of the projectsite.

None
assetlocation str

Title of the asset location.

None
subassembly_type str

Type of the subassemblies (e.g. 'MP', 'TW', 'TP').

None
subassembly_id int or int64

ID of the subassembly.

None

Returns:

Type Description
dict

Dictionary with the following keys:

  • "data": Pandas dataframe with the location data for each project
  • "exists": Boolean indicating whether matching records are found

Examples:

    >>> from unittest import mock
    >>> api = GeometryAPI(
    ...     api_root="https://example",
    ...     header={"Authorization": "Token test"},
    ... )
    >>> df = pd.DataFrame({"id": [1]})
    >>> with mock.patch.object(
    ...     GeometryAPI,
    ...     "process_data",
    ...     return_value=(df, {"existance": True}),
    ... ):
    ...     out = api.get_buildingblocks(projectsite="Site")
    >>> out["exists"]
    True
Source code in src/owi/metadatabase/geometry/io.py
def get_buildingblocks(
    self,
    projectsite: Union[str, None] = None,
    assetlocation: Union[str, None] = None,
    subassembly_type: Union[str, None] = None,
    subassembly_id: Union[int, np.int64, None] = None,
) -> dict[str, Union[pd.DataFrame, bool, np.int64, None]]:
    """
    Get all relevant building blocks.

    Parameters
    ----------
    projectsite : str, optional
        Title of the projectsite.
    assetlocation : str, optional
        Title of the asset location.
    subassembly_type : str, optional
        Type of the subassemblies (e.g. 'MP', 'TW', 'TP').
    subassembly_id : int or np.int64, optional
        ID of the subassembly.

    Returns
    -------
    dict
        Dictionary with the following keys:

        - "data": Pandas dataframe with the location data for each
          project
        - "exists": Boolean indicating whether matching records
          are found

    Examples
    --------
            >>> from unittest import mock
            >>> api = GeometryAPI(
            ...     api_root="https://example",
            ...     header={"Authorization": "Token test"},
            ... )
            >>> df = pd.DataFrame({"id": [1]})
            >>> with mock.patch.object(
            ...     GeometryAPI,
            ...     "process_data",
            ...     return_value=(df, {"existance": True}),
            ... ):
            ...     out = api.get_buildingblocks(projectsite="Site")
            >>> out["exists"]
            True
    """
    url_params = {}
    if projectsite is not None:
        url_params["sub_assembly__asset__projectsite__title"] = projectsite
    if assetlocation is not None:
        url_params["sub_assembly__asset__title"] = assetlocation
    if subassembly_type is not None:
        url_params["sub_assembly__subassembly_type"] = subassembly_type
    if subassembly_id is not None:
        url_params["sub_assembly__id"] = str(subassembly_id)
    url_data_type = "buildingblocks"
    output_type = "list"
    df, df_add = self.process_data(url_data_type, url_params, output_type)
    return {"data": df, "exists": df_add["existance"]}
get_materials
get_materials()

Get all the materials of building blocks.

Returns:

Type Description
dict

Dictionary with the following keys:

  • "data": Pandas dataframe with the location data for each project
  • "exists": Boolean indicating whether matching records are found

Examples:

    >>> from unittest import mock
    >>> api = GeometryAPI(
    ...     api_root="https://example",
    ...     header={"Authorization": "Token test"},
    ... )
    >>> df = pd.DataFrame({"id": [1]})
    >>> with mock.patch.object(
    ...     GeometryAPI,
    ...     "process_data",
    ...     return_value=(df, {"existance": True}),
    ... ):
    ...     out = api.get_materials()
    >>> out["exists"]
    True
Source code in src/owi/metadatabase/geometry/io.py
def get_materials(
    self,
) -> dict[str, Union[pd.DataFrame, bool, np.int64, None]]:
    """
    Get all the materials of building blocks.

    Returns
    -------
    dict
        Dictionary with the following keys:

        - "data": Pandas dataframe with the location data for each
          project
        - "exists": Boolean indicating whether matching records
          are found

    Examples
    --------
            >>> from unittest import mock
            >>> api = GeometryAPI(
            ...     api_root="https://example",
            ...     header={"Authorization": "Token test"},
            ... )
            >>> df = pd.DataFrame({"id": [1]})
            >>> with mock.patch.object(
            ...     GeometryAPI,
            ...     "process_data",
            ...     return_value=(df, {"existance": True}),
            ... ):
            ...     out = api.get_materials()
            >>> out["exists"]
            True
    """
    url_params = {}  # type: dict[str, str]
    url_data_type = "materials"
    output_type = "list"
    df, df_add = self.process_data(url_data_type, url_params, output_type)
    return {"data": df, "exists": df_add["existance"]}
get_subassembly_objects
get_subassembly_objects(
    turbine, subassembly=None, model_definition_id=None
)

Get all subassemblies for a given turbine, divided by type.

Parameters:

Name Type Description Default
turbine str

Turbine title.

required
subassembly str

Sub-assembly type (e.g. 'MP', 'TW', 'TP').

None
model_definition_id int or int64

ID of the model definition to filter the subassemblies.

None

Returns:

Type Description
dict

Dictionary with the following keys:

  • "TW": SubAssembly object for the tower
  • "TP": SubAssembly object for the transition piece
  • "MP": SubAssembly object for the monopile

Raises:

Type Description
ValueError

If no subassemblies found for the turbine or if no materials found in the database.

Examples:

>>> from types import SimpleNamespace
>>> from unittest import mock
>>> materials = pd.DataFrame(
...     [
...         {
...             "title": "Steel",
...             "slug": "steel",
...             "id": np.int64(1),
...             "description": "",
...             "young_modulus": np.float64(210000.0),
...             "density": np.float64(7850.0),
...             "poisson_ratio": np.float64(0.3),
...         }
...     ]
... )
>>> item = {
...     "id": np.int64(1),
...     "title": "SA_1",
...     "description": "",
...     "slug": "sa",
...     "x_position": np.float64(0),
...     "y_position": np.float64(0),
...     "z_position": np.float64(0),
...     "vertical_position_reference_system": "LAT",
...     "subassembly_type": "TW",
...     "source": "api",
...     "asset": np.int64(1),
...     "model_definition": np.int64(1),
... }
>>> response = SimpleNamespace(
...     status_code=200,
...     reason="OK",
...     json=lambda: [item],
... )
>>> api = GeometryAPI(
...     api_root="https://example",
...     header={"Authorization": "Token test"},
... )
>>> with mock.patch.object(
...     GeometryAPI,
...     "send_request",
...     return_value=response,
... ), mock.patch.object(
...     GeometryAPI,
...     "get_materials",
...     return_value={"exists": True, "data": materials},
... ):
...     out = api.get_subassembly_objects("T01")
>>> sorted(out.keys())
['TW']
Source code in src/owi/metadatabase/geometry/io.py
def get_subassembly_objects(
    self,
    turbine: str,
    subassembly: Union[str, None] = None,
    model_definition_id: Union[int, np.int64, None] = None,
) -> dict[str, SubAssembly]:
    """
    Get all subassemblies for a given turbine, divided by type.

    Parameters
    ----------
    turbine : str
        Turbine title.
    subassembly : str, optional
        Sub-assembly type (e.g. 'MP', 'TW', 'TP').
    model_definition_id : int or np.int64, optional
        ID of the model definition to filter the subassemblies.

    Returns
    -------
    dict
        Dictionary with the following keys:

        - "TW": SubAssembly object for the tower
        - "TP": SubAssembly object for the transition piece
        - "MP": SubAssembly object for the monopile

    Raises
    ------
    ValueError
        If no subassemblies found for the turbine or if no
        materials found in the database.

    Examples
    --------
    >>> from types import SimpleNamespace
    >>> from unittest import mock
    >>> materials = pd.DataFrame(
    ...     [
    ...         {
    ...             "title": "Steel",
    ...             "slug": "steel",
    ...             "id": np.int64(1),
    ...             "description": "",
    ...             "young_modulus": np.float64(210000.0),
    ...             "density": np.float64(7850.0),
    ...             "poisson_ratio": np.float64(0.3),
    ...         }
    ...     ]
    ... )
    >>> item = {
    ...     "id": np.int64(1),
    ...     "title": "SA_1",
    ...     "description": "",
    ...     "slug": "sa",
    ...     "x_position": np.float64(0),
    ...     "y_position": np.float64(0),
    ...     "z_position": np.float64(0),
    ...     "vertical_position_reference_system": "LAT",
    ...     "subassembly_type": "TW",
    ...     "source": "api",
    ...     "asset": np.int64(1),
    ...     "model_definition": np.int64(1),
    ... }
    >>> response = SimpleNamespace(
    ...     status_code=200,
    ...     reason="OK",
    ...     json=lambda: [item],
    ... )
    >>> api = GeometryAPI(
    ...     api_root="https://example",
    ...     header={"Authorization": "Token test"},
    ... )
    >>> with mock.patch.object(
    ...     GeometryAPI,
    ...     "send_request",
    ...     return_value=response,
    ... ), mock.patch.object(
    ...     GeometryAPI,
    ...     "get_materials",
    ...     return_value={"exists": True, "data": materials},
    ... ):
    ...     out = api.get_subassembly_objects("T01")
    >>> sorted(out.keys())
    ['TW']
    """
    url_data_type = "subassemblies"
    url_params = {"asset__title": turbine}
    if subassembly is not None:
        url_params["subassembly_type"] = subassembly
    if model_definition_id is not None:
        url_params["model_definition"] = str(model_definition_id)
    resp = self.send_request(url_data_type, url_params)
    self.check_request_health(resp)
    if not resp.json():
        raise ValueError("No subassemblies found for " + str(turbine))

    material_data = self.get_materials()
    if material_data["exists"]:
        materials = material_data["data"]
    else:
        raise ValueError("No materials found in the database.")

    subassemblies = {}
    for item in resp.json():
        subassembly_type = item["subassembly_type"]
        subassembly_obj = SubAssembly(materials, item, api_object=self)
        if subassembly_type in subassemblies:
            if not isinstance(subassemblies[subassembly_type], list):
                subassemblies[subassembly_type] = [subassemblies[subassembly_type]]
            subassemblies[subassembly_type].append(subassembly_obj)
        else:
            subassemblies[subassembly_type] = subassembly_obj

    return subassemblies
get_owt_geometry_processor
get_owt_geometry_processor(
    turbines,
    model_definition=None,
    tower_base=None,
    monopile_head=None,
)

Return the required processing class.

Will return data even if some turbines have issues given that at least one is fully OK.

Parameters:

Name Type Description Default
turbines str or list of str

Title(s) of the turbines.

required
model_definition str

Title of the model definition.

None
tower_base float or list of float

Height(s) of the tower base.

None
monopile_head float or list of float

Height(s) of the monopile head.

None

Returns:

Type Description
OWTs

Object containing information about all the requested turbines.

Raises:

Type Description
ValueError

If no materials found in the database or if all turbines encounter processing errors.

Examples:

>>> from unittest import mock
>>> api = GeometryAPI(
...     api_root="https://example",
...     header={"Authorization": "Token test"},
... )
>>> materials = pd.DataFrame({"id": [1]})
>>> location = pd.DataFrame({"projectsite_name": ["Site"]})
>>> subassemblies = pd.DataFrame({"subassembly_type": ["TW"]})
>>> def _make_owt(*args, **kwargs):
...     return "owt"
>>> def _make_owts(turbines, owts):
...     return {"turbines": turbines, "owts": owts}
>>> with mock.patch.object(
...     GeometryAPI,
...     "get_materials",
...     return_value={"exists": True, "data": materials},
... ), mock.patch.object(
...     api.loc_api,
...     "get_assetlocation_detail",
...     return_value={"exists": True, "data": location},
... ), mock.patch.object(
...     GeometryAPI,
...     "get_subassemblies",
...     return_value={"exists": True, "data": subassemblies},
... ), mock.patch.object(
...     GeometryAPI,
...     "_check_if_need_modeldef",
...     return_value=None,
... ), mock.patch(
...     "geometry.io.OWT",
...     _make_owt,
... ), mock.patch(
...     "geometry.io.OWTs",
...     _make_owts,
... ):
...     out = api.get_owt_geometry_processor("T01")
>>> out["turbines"]
['T01']
Source code in src/owi/metadatabase/geometry/io.py
def get_owt_geometry_processor(
    self,
    turbines: Union[str, list[str]],
    model_definition: Union[str, None] = None,
    tower_base: Union[float, list[float], None] = None,
    monopile_head: Union[float, list[float], None] = None,
) -> OWTs:
    """
    Return the required processing class.

    Will return data even if some turbines have issues given that at
    least one is fully OK.

    Parameters
    ----------
    turbines : str or list of str
        Title(s) of the turbines.
    model_definition : str, optional
        Title of the model definition.
    tower_base : float or list of float, optional
        Height(s) of the tower base.
    monopile_head : float or list of float, optional
        Height(s) of the monopile head.

    Returns
    -------
    OWTs
        Object containing information about all the requested
        turbines.

    Raises
    ------
    ValueError
        If no materials found in the database or if all turbines
        encounter processing errors.

    Examples
    --------
    >>> from unittest import mock
    >>> api = GeometryAPI(
    ...     api_root="https://example",
    ...     header={"Authorization": "Token test"},
    ... )
    >>> materials = pd.DataFrame({"id": [1]})
    >>> location = pd.DataFrame({"projectsite_name": ["Site"]})
    >>> subassemblies = pd.DataFrame({"subassembly_type": ["TW"]})
    >>> def _make_owt(*args, **kwargs):
    ...     return "owt"
    >>> def _make_owts(turbines, owts):
    ...     return {"turbines": turbines, "owts": owts}
    >>> with mock.patch.object(
    ...     GeometryAPI,
    ...     "get_materials",
    ...     return_value={"exists": True, "data": materials},
    ... ), mock.patch.object(
    ...     api.loc_api,
    ...     "get_assetlocation_detail",
    ...     return_value={"exists": True, "data": location},
    ... ), mock.patch.object(
    ...     GeometryAPI,
    ...     "get_subassemblies",
    ...     return_value={"exists": True, "data": subassemblies},
    ... ), mock.patch.object(
    ...     GeometryAPI,
    ...     "_check_if_need_modeldef",
    ...     return_value=None,
    ... ), mock.patch(
    ...     "geometry.io.OWT",
    ...     _make_owt,
    ... ), mock.patch(
    ...     "geometry.io.OWTs",
    ...     _make_owts,
    ... ):
    ...     out = api.get_owt_geometry_processor("T01")
    >>> out["turbines"]
    ['T01']
    """
    materials_data = self.get_materials()
    if materials_data["exists"]:
        materials = cast(pd.DataFrame, materials_data["data"])
    else:
        raise ValueError("No materials found in the database.")
    owts = []
    successful_turbines = []
    errors = []
    turbines = [turbines] if isinstance(turbines, str) else turbines
    if not isinstance(tower_base, list) and not isinstance(monopile_head, list):
        tower_base = [tower_base] * len(turbines)  # type: ignore
        monopile_head = [monopile_head] * len(turbines)  # type: ignore
    for i, turbine in enumerate(turbines):
        try:
            location_data = self.loc_api.get_assetlocation_detail(assetlocation=turbine)
            if location_data["exists"]:
                location = cast(pd.DataFrame, location_data["data"])
            else:
                raise ValueError(f"No location found for turbine {turbine}.")
            projectsite = location["projectsite_name"].loc[0]
            subassemblies_data = self.get_subassemblies(
                projectsite=projectsite,
                assetlocation=turbine,
                model_definition=model_definition,
            )
            if subassemblies_data["exists"]:
                subassemblies = subassemblies_data["data"]
                self._check_if_need_modeldef(subassemblies, turbine)
            else:
                raise ValueError(
                    f"No subassemblies found for turbine {turbine}. Please check model definition or database data."
                )
            owts.append(
                OWT(
                    self,
                    materials,
                    subassemblies,
                    location,
                    tower_base[i] if isinstance(tower_base, list) else tower_base,
                    (monopile_head[i] if isinstance(monopile_head, list) else monopile_head),
                )
            )
            successful_turbines.append(turbine)
        except ValueError as e:
            errors.append(str(e))
    if errors:
        if successful_turbines:
            warnings.warn(
                f"There were some errors during processing the request. "
                f"But some turbines were processed successfully: {', '.join(successful_turbines)}."
                f"\nErrors:\n" + "\n".join(errors),
                stacklevel=2,
            )
        else:
            raise ValueError("\n".join(errors))
    return OWTs(successful_turbines, owts)
get_monopile_pyles
get_monopile_pyles(
    projectsite,
    assetlocation,
    cutoff_point=nan,
    model_definition=None,
)

Return a dataframe with the monopile geometry.

Uses the mudline as reference.

Parameters:

Name Type Description Default
projectsite str

Name of the project site.

required
assetlocation str

Name of the wind turbine location.

required
cutoff_point float

Elevation of the load application point in (mLAT) above the mudline.

nan
model_definition str

Title of the model definition.

None

Returns:

Type Description
DataFrame

Dataframe with the monopile geometry.

Raises:

Type Description
ValueError

If no subassemblies or location found for turbine.

Examples:

>>> from unittest import mock
>>> api = GeometryAPI(
...     api_root="https://example",
...     header={"Authorization": "Token test"},
... )
>>> bbs = pd.DataFrame(
...     [
...         {
...             "z_position": 0,
...             "material_name": "Steel",
...             "density": 7850.0,
...             "wall_thickness": 20.0,
...             "bottom_outer_diameter": 6.0,
...             "top_outer_diameter": 6.0,
...             "youngs_modulus": 210000.0,
...             "poissons_ratio": 0.3,
...         },
...         {
...             "z_position": -1000,
...             "material_name": "Steel",
...             "density": 7850.0,
...             "wall_thickness": 20.0,
...             "bottom_outer_diameter": 6.0,
...             "top_outer_diameter": 6.0,
...             "youngs_modulus": 210000.0,
...             "poissons_ratio": 0.3,
...         },
...     ]
... )
>>> sas = pd.DataFrame({"z_position": [-50000]})
>>> location = pd.DataFrame({"elevation": [30.0]})
>>> with mock.patch.object(
...     GeometryAPI,
...     "get_buildingblocks",
...     return_value={"exists": True, "data": bbs},
... ), mock.patch.object(
...     GeometryAPI,
...     "get_subassemblies",
...     return_value={"exists": True, "data": sas},
... ), mock.patch.object(
...     GeometryAPI,
...     "_check_if_need_modeldef",
...     return_value=None,
... ), mock.patch.object(
...     api.loc_api,
...     "get_assetlocation_detail",
...     return_value={"exists": True, "data": location},
... ):
...     pile = api.get_monopile_pyles("Site", "T01")
>>> "Depth from [m]" in pile.columns
True
Source code in src/owi/metadatabase/geometry/io.py
def get_monopile_pyles(
    self,
    projectsite,
    assetlocation,
    cutoff_point=np.nan,
    model_definition: Union[str, None] = None,
):
    """
    Return a dataframe with the monopile geometry.

    Uses the mudline as reference.

    Parameters
    ----------
    projectsite : str
        Name of the project site.
    assetlocation : str
        Name of the wind turbine location.
    cutoff_point : float, optional
        Elevation of the load application point in (mLAT) above the
        mudline.
    model_definition : str, optional
        Title of the model definition.

    Returns
    -------
    pd.DataFrame
        Dataframe with the monopile geometry.

    Raises
    ------
    ValueError
        If no subassemblies or location found for turbine.

    Examples
    --------
    >>> from unittest import mock
    >>> api = GeometryAPI(
    ...     api_root="https://example",
    ...     header={"Authorization": "Token test"},
    ... )
    >>> bbs = pd.DataFrame(
    ...     [
    ...         {
    ...             "z_position": 0,
    ...             "material_name": "Steel",
    ...             "density": 7850.0,
    ...             "wall_thickness": 20.0,
    ...             "bottom_outer_diameter": 6.0,
    ...             "top_outer_diameter": 6.0,
    ...             "youngs_modulus": 210000.0,
    ...             "poissons_ratio": 0.3,
    ...         },
    ...         {
    ...             "z_position": -1000,
    ...             "material_name": "Steel",
    ...             "density": 7850.0,
    ...             "wall_thickness": 20.0,
    ...             "bottom_outer_diameter": 6.0,
    ...             "top_outer_diameter": 6.0,
    ...             "youngs_modulus": 210000.0,
    ...             "poissons_ratio": 0.3,
    ...         },
    ...     ]
    ... )
    >>> sas = pd.DataFrame({"z_position": [-50000]})
    >>> location = pd.DataFrame({"elevation": [30.0]})
    >>> with mock.patch.object(
    ...     GeometryAPI,
    ...     "get_buildingblocks",
    ...     return_value={"exists": True, "data": bbs},
    ... ), mock.patch.object(
    ...     GeometryAPI,
    ...     "get_subassemblies",
    ...     return_value={"exists": True, "data": sas},
    ... ), mock.patch.object(
    ...     GeometryAPI,
    ...     "_check_if_need_modeldef",
    ...     return_value=None,
    ... ), mock.patch.object(
    ...     api.loc_api,
    ...     "get_assetlocation_detail",
    ...     return_value={"exists": True, "data": location},
    ... ):
    ...     pile = api.get_monopile_pyles("Site", "T01")
    >>> "Depth from [m]" in pile.columns
    True
    """
    # Retrieve the monopile cans
    bbs = self.get_buildingblocks(
        projectsite=projectsite,
        assetlocation=assetlocation,
        subassembly_type="MP",
    )
    # Retrieve the monopile subassembly
    sas = self.get_subassemblies(
        projectsite=projectsite,
        assetlocation=assetlocation,
        subassembly_type="MP",
        model_definition=model_definition,
    )
    if sas["exists"]:
        subassemblies = cast(pd.DataFrame, sas["data"])
        self._check_if_need_modeldef(subassemblies, assetlocation)
    else:
        raise ValueError(
            f"No subassemblies found for turbine {assetlocation}. Please check model definition or database data."
        )
    # Water depth
    location_data = self.loc_api.get_assetlocation_detail(assetlocation=assetlocation, projectsite=projectsite)
    if location_data["exists"]:
        location = cast(pd.DataFrame, location_data["data"])
        water_depth = location["elevation"].values[0]
    else:
        raise ValueError(
            f"No location found for turbine {assetlocation} and hence no water depth can be retrieved."
        )

    # Calculate the pile penetration
    sas_df = cast(pd.DataFrame, sas["data"])
    toe_depth_lat = sas_df["z_position"].iloc[0]
    penetration = -((1e-3 * toe_depth_lat) - water_depth)

    # Create the pile for subsequent response analysis
    pile = pd.DataFrame()

    bbs_df = cast(pd.DataFrame, bbs["data"])
    for index in range(1, len(bbs_df)):
        prev_row = bbs_df.iloc[index - 1]
        row = bbs_df.iloc[index]
        pile.loc[index, "Depth to [m]"] = penetration - 1e-3 * float(prev_row.at["z_position"])
        pile.loc[index, "Depth from [m]"] = penetration - 1e-3 * float(row.at["z_position"])
        pile.loc[index, "Pile material"] = str(row.at["material_name"])
        pile.loc[index, "Pile material submerged unit weight [kN/m3]"] = 1e-2 * float(row.at["density"]) - 10
        pile.loc[index, "Wall thickness [mm]"] = float(row.at["wall_thickness"])
        pile.loc[index, "Diameter [m]"] = (
            1e-3 * 0.5 * (float(row.at["bottom_outer_diameter"]) + float(row.at["top_outer_diameter"]))
        )
        pile.loc[index, "Youngs modulus [GPa]"] = float(row.at["youngs_modulus"])
        pile.loc[index, "Poissons ratio [-]"] = float(row.at["poissons_ratio"])

    pile.sort_values("Depth from [m]", inplace=True)
    pile.reset_index(drop=True, inplace=True)

    # Cut off at the mudline
    if not np.isnan(cutoff_point):
        pile = pile.loc[pile["Depth to [m]"] > cutoff_point].reset_index(drop=True)
        pile.loc[0, "Depth from [m]"] = cutoff_point

    return pile
plot_turbines
plot_turbines(
    turbines,
    figures_per_line=5,
    return_fig=False,
    model_definition=None,
)

Plot turbines' frontal geometry.

Parameters:

Name Type Description Default
turbines str or list of str

Title(s) of the turbines.

required
figures_per_line int

Number of figures per line, default is 5.

5
return_fig bool

Boolean indicating whether to return the figure, default is False.

False
model_definition str

Title of the model definition.

None

Returns:

Type Description
Figure or None

Plotly figure object with selected turbines front profiles (if requested) or nothing.

Raises:

Type Description
ValueError

If no materials or subassemblies found in the database.

Examples:

>>> from unittest import mock
>>> class _StubSubassembly:
...     def __init__(self, *args, **kwargs):
...         self.building_blocks = []
...     def plotly(self):
...         layout = {
...             "scene": {},
...             "yaxis": {
...                 "title": {"text": "Height , mm"},
...                 "scaleanchor": "x",
...                 "scaleratio": 1,
...                 "type": "linear",
...             },
...         }
...         return [], layout
>>> api = GeometryAPI(
...     api_root="https://example",
...     header={"Authorization": "Token test"},
... )
>>> materials = pd.DataFrame({"id": [1]})
>>> subassemblies = pd.DataFrame({"subassembly_type": ["TW"]})
>>> with mock.patch.object(
...     GeometryAPI,
...     "get_materials",
...     return_value={"exists": True, "data": materials},
... ), mock.patch.object(
...     GeometryAPI,
...     "get_subassemblies",
...     return_value={"exists": True, "data": subassemblies},
... ), mock.patch.object(
...     GeometryAPI,
...     "_check_if_need_modeldef",
...     return_value=None,
... ), mock.patch(
...     "geometry.io.SubAssembly",
...     _StubSubassembly,
... ):
...     fig = api.plot_turbines(["T01"], return_fig=True)
>>> fig is not None
True
Source code in src/owi/metadatabase/geometry/io.py
def plot_turbines(
    self,
    turbines: Union[list[str], str],
    figures_per_line: int = 5,
    return_fig: bool = False,
    model_definition: Union[str, None] = None,
) -> Union[go.Figure, None]:
    """
    Plot turbines' frontal geometry.

    Parameters
    ----------
    turbines : str or list of str
        Title(s) of the turbines.
    figures_per_line : int, optional
        Number of figures per line, default is 5.
    return_fig : bool, optional
        Boolean indicating whether to return the figure, default
        is False.
    model_definition : str, optional
        Title of the model definition.

    Returns
    -------
    plotly.graph_objects.Figure or None
        Plotly figure object with selected turbines front profiles
        (if requested) or nothing.

    Raises
    ------
    ValueError
        If no materials or subassemblies found in the database.

    Examples
    --------
    >>> from unittest import mock
    >>> class _StubSubassembly:
    ...     def __init__(self, *args, **kwargs):
    ...         self.building_blocks = []
    ...     def plotly(self):
    ...         layout = {
    ...             "scene": {},
    ...             "yaxis": {
    ...                 "title": {"text": "Height , mm"},
    ...                 "scaleanchor": "x",
    ...                 "scaleratio": 1,
    ...                 "type": "linear",
    ...             },
    ...         }
    ...         return [], layout
    >>> api = GeometryAPI(
    ...     api_root="https://example",
    ...     header={"Authorization": "Token test"},
    ... )
    >>> materials = pd.DataFrame({"id": [1]})
    >>> subassemblies = pd.DataFrame({"subassembly_type": ["TW"]})
    >>> with mock.patch.object(
    ...     GeometryAPI,
    ...     "get_materials",
    ...     return_value={"exists": True, "data": materials},
    ... ), mock.patch.object(
    ...     GeometryAPI,
    ...     "get_subassemblies",
    ...     return_value={"exists": True, "data": subassemblies},
    ... ), mock.patch.object(
    ...     GeometryAPI,
    ...     "_check_if_need_modeldef",
    ...     return_value=None,
    ... ), mock.patch(
    ...     "geometry.io.SubAssembly",
    ...     _StubSubassembly,
    ... ):
    ...     fig = api.plot_turbines(["T01"], return_fig=True)
    >>> fig is not None
    True
    """
    materials_data = self.get_materials()
    if materials_data["exists"]:
        materials = materials_data["data"]
    else:
        raise ValueError("No materials found in the database.")
    turbines = [turbines] if isinstance(turbines, str) else turbines
    if len(turbines) > figures_per_line:
        n_rows = len(turbines) // figures_per_line + 1
        n_cols = figures_per_line
        rows = [i for i in range(1, n_rows + 1) for _ in range(n_cols)]
        cols = [i for _ in range(n_rows) for i in range(1, n_cols + 1)]
    else:
        n_rows = 1
        n_cols = len(turbines)
        rows = [1 for _ in range(n_cols)]
        cols = list(range(1, n_cols + 1))
    autosize = not len(turbines) < 3
    fig = make_subplots(n_rows, n_cols, subplot_titles=turbines)
    for i, turbine in enumerate(turbines):
        subassemblies_data = self.get_subassemblies(
            assetlocation=turbine,
            model_definition=model_definition,
        )
        if subassemblies_data["exists"]:
            subassemblies = cast(pd.DataFrame, subassemblies_data["data"])
            self._check_if_need_modeldef(subassemblies, turbine)
        else:
            raise ValueError(
                f"No subassemblies found for turbine {turbine}. Please check model definition or database data."
            )
        for _, sa in subassemblies.iterrows():
            subassembly = SubAssembly(materials, cast(DataSA, sa.to_dict()), api_object=self)
            subassembly.building_blocks  # noqa: B018
            plotly_data = subassembly.plotly()
            for k in range(len(plotly_data[0])):
                fig.add_trace(plotly_data[0][k], row=rows[i], col=cols[i])
        plotly_layout = plotly_data[1]
        if i > 0:
            plotly_layout["scene" + str(i + 1)] = plotly_layout["scene"]
            plotly_layout["yaxis" + str(i + 1)] = plotly_layout["yaxis"]
            plotly_layout["yaxis" + str(i + 1)]["scaleanchor"] = "x" + str(i + 1)
            plotly_layout.pop("scene")
            plotly_layout.pop("yaxis")
            plotly_layout["yaxis" + str(i + 1)].pop("title")
        fig.update_layout(plotly_layout, autosize=autosize)
    if return_fig:
        return fig
    else:
        fig.show()

OWT

OWT(
    api,
    materials,
    subassemblies,
    location,
    tower_base=None,
    pile_head=None,
)

Class to process the geometry data of a single OWT.

:param api: API object used to call get_* methods. :param materials: Pandas dataframe with the materials data. :param sub_assemblies: Dictionary of the subassemblies. :param tw_sub_assemblies: Pandas dataframe with the tower subassemblies data for a given turbine. :param tp_sub_assemblies: Pandas dataframe with the transition piece subassemblies data for a given turbine. :param mp_sub_assemblies: Pandas dataframe with the monopile subassemblies data for a given turbine. :param tower_base: Elevation of the OWT tower base in mLAT. :param pile_head: Elevation of the pile head in mLAT. :param water_depth: Water depth in mLAT. :param pile_toe: Elevation of the pile toe in mLAT. :param rna: Pandas dataframe with the RNA data. :param tower: Pandas dataframe with the tower data. :param transition_piece: Pandas dataframe with the transition piece data. :param monopile: Pandas dataframe with the monopile data. :param tw_lumped_mass: Pandas dataframe with the lumped masses data for the tower. :param tp_lumped_mass: Pandas dataframe with the lumped masses data for the transition piece. :param mp_lumped_mass: Pandas dataframe with the lumped masses data for the monopile. :param tp_distributed_mass: Pandas dataframe with the distributed masses data for the transition piece. :param mp_distributed_mass: Pandas dataframe with the distributed masses data for the monopile. :param grout: Pandas dataframe with the grout data. :param full_structure: Pandas dataframe with the full structure data. :param tp_skirt: Pandas dataframe with the transition piece skirt data. :param substructure: Pandas dataframe with the substructure data.

Create an instance of the OWT class with required parameters.

Parameters:

Name Type Description Default
api Any

API object used to call get_* methods.

required
materials DataFrame or bool or int64 or None

Pandas dataframe with the materials data.

required
subassemblies DataFrame or bool or int64 or None

Pandas dataframe with the subassemblies data for a given turbine.

required
location DataFrame or bool or int64 or None

Pandas dataframe with the location data for a given turbine.

required
tower_base float64

Elevation of the OWT tower base in mLAT.

None
pile_head float64

Elevation of the pile head in mLAT.

None

Examples:

>>> from contextlib import ExitStack
>>> from unittest import mock
>>> location = pd.DataFrame({"elevation": [30.0]})
>>> def _set_subassemblies(self, subassemblies):
...     self.sub_assemblies = {}
>>> def _set_members(self):
...     return None
>>> with mock.patch.object(
...     OWT,
...     "_set_subassemblies",
...     _set_subassemblies,
... ), mock.patch.object(OWT, "_set_members", _set_members):
...     owt = OWT(
...         api=object(),
...         materials=pd.DataFrame(),
...         subassemblies=pd.DataFrame(),
...         location=location,
...     )
>>> float(owt.water_depth)
30.0
Source code in src/owi/metadatabase/geometry/processing.py
def __init__(
    self,
    api: Any,
    materials: Union[pd.DataFrame, bool, np.int64, None],
    subassemblies: Union[pd.DataFrame, bool, np.int64, None],
    location: Union[pd.DataFrame, bool, np.int64, None],
    tower_base: Union[np.float64, float, None] = None,
    pile_head: Union[np.float64, float, None] = None,
) -> None:
    """
    Create an instance of the OWT class with required parameters.

    Parameters
    ----------
    api : Any
        API object used to call get_* methods.
    materials : pd.DataFrame or bool or np.int64 or None
        Pandas dataframe with the materials data.
    subassemblies : pd.DataFrame or bool or np.int64 or None
        Pandas dataframe with the subassemblies data for a given
        turbine.
    location : pd.DataFrame or bool or np.int64 or None
        Pandas dataframe with the location data for a given
        turbine.
    tower_base : np.float64, optional
        Elevation of the OWT tower base in mLAT.
    pile_head : np.float64, optional
        Elevation of the pile head in mLAT.

    Examples
    --------
    >>> from contextlib import ExitStack
    >>> from unittest import mock
    >>> location = pd.DataFrame({"elevation": [30.0]})
    >>> def _set_subassemblies(self, subassemblies):
    ...     self.sub_assemblies = {}
    >>> def _set_members(self):
    ...     return None
    >>> with mock.patch.object(
    ...     OWT,
    ...     "_set_subassemblies",
    ...     _set_subassemblies,
    ... ), mock.patch.object(OWT, "_set_members", _set_members):
    ...     owt = OWT(
    ...         api=object(),
    ...         materials=pd.DataFrame(),
    ...         subassemblies=pd.DataFrame(),
    ...         location=location,
    ...     )
    >>> float(owt.water_depth)
    30.0
    """
    self._init_proc = False
    self._init_spec_part = False
    self._init_spec_full = False
    self.api = api
    materials_df = cast(pd.DataFrame, materials)
    subassemblies_df = cast(pd.DataFrame, subassemblies)
    location_df = cast(pd.DataFrame, location)
    self.materials = materials_df
    self._set_subassemblies(subassemblies_df)
    self.tw_sub_assemblies = None
    self.tp_sub_assemblies = None
    self.mp_sub_assemblies = None
    self._set_members()
    for attr in ATTR_PROC:
        setattr(self, attr, None)
    for attr in ATTR_SPEC:
        setattr(self, attr, None)
    self.water_depth = np.float64(location_df["elevation"].values[0])
    if not tower_base or not pile_head:
        if "TW" in self.sub_assemblies:
            self.tower_base = self.sub_assemblies["TW"].absolute_bottom
        elif "TP" in self.sub_assemblies:
            self.tower_base = self.sub_assemblies["TP"].absolute_top
        else:
            self.tower_base = None
        if "MP" in self.sub_assemblies:
            self.pile_head = self.sub_assemblies["MP"].absolute_top
        else:
            self.pile_head = None
    else:
        self.tower_base = tower_base
        self.pile_head = pile_head
Functions
set_df_structure
set_df_structure(idx)

Calculate and/or convert geometrical data of subassemblies.

Calculates and/or converts geometrical data of subassemblies from the database.

Parameters:

Name Type Description Default
idx str

Possible index to identify corresponding subassembly.

required

Returns:

Type Description
DataFrame

Dataframe containing geometry data from database with z in mLAT system.

Raises:

Type Description
ValueError

If subassembly data not found or unknown index.

Source code in src/owi/metadatabase/geometry/processing.py
def set_df_structure(self, idx: str) -> pd.DataFrame:
    """
    Calculate and/or convert geometrical data of subassemblies.

    Calculates and/or converts geometrical data of subassemblies
    from the database.

    Parameters
    ----------
    idx : str
        Possible index to identify corresponding subassembly.

    Returns
    -------
    pd.DataFrame
        Dataframe containing geometry data from database with z in
        mLAT system.

    Raises
    ------
    ValueError
        If subassembly data not found or unknown index.
    """
    cols = [
        "OD",
        "height",
        "mass",
        "volume",
        "wall_thickness",
        "x",
        "y",
        "z",
    ]
    if idx == "tw":
        if self.tw_sub_assemblies is None:
            raise ValueError("Tower subassembly data not found.")
        df_index = self.tw_sub_assemblies.index.str.contains(idx)
        df = deepcopy(self.tw_sub_assemblies.loc[df_index, cols])
        depth_to = self.tower_base + df.z * 1e-3
        depth_from = depth_to + df.height * 1e-3
    elif idx == "tp":
        if self.tp_sub_assemblies is None:
            raise ValueError("Transition piece subassembly data not found.")
        # We don't take into account the grout, this element will be modelled as a distributed lumped mass.
        df_index = (self.tp_sub_assemblies.index.str.contains(idx)) & (
            ~self.tp_sub_assemblies.index.str.contains("grout")
        )
        df = deepcopy(self.tp_sub_assemblies.loc[df_index, cols])
        bottom_tp = self.tower_base - df["height"].sum() * 1e-3
        depth_to = bottom_tp + df.z * 1e-3
        depth_from = depth_to + df.height * 1e-3
    elif idx == "mp":
        if self.mp_sub_assemblies is None:
            raise ValueError("Monopile subassembly data not found.")
        df_index = self.mp_sub_assemblies.index.str.contains(idx)
        df = deepcopy(self.mp_sub_assemblies.loc[df_index, cols])
        toe = self.pile_head - df["height"].sum() * 1e-3
        self.pile_toe = round(toe, 3)
        depth_to = toe + df.z * 1e-3
        depth_from = depth_to + df.height * 1e-3
    else:
        raise ValueError("Unknown index.")
    df["Elevation from [mLAT]"] = depth_from
    df["Elevation to [mLAT]"] = depth_to
    # Round elevations to mm to avoid numerical inconsistencies later when setting altitude values to apply loads.
    df = df.round({"Elevation from [mLAT]": 3, "Elevation to [mLAT]": 3})
    return df
process_structure_geometry
process_structure_geometry(idx)

Calculate and/or convert geometrical data for FE models.

Calculates and/or converts geometrical data of subassemblies from the database to use as input for FE models.

Parameters:

Name Type Description Default
idx str

Possible index to identify corresponding subassembly.

required

Returns:

Type Description
DataFrame

Dataframe consisting of the required data to build FE models.

Source code in src/owi/metadatabase/geometry/processing.py
def process_structure_geometry(self, idx: str) -> pd.DataFrame:
    """
    Calculate and/or convert geometrical data for FE models.

    Calculates and/or converts geometrical data of subassemblies
    from the database to use as input for FE models.

    Parameters
    ----------
    idx : str
        Possible index to identify corresponding subassembly.

    Returns
    -------
    pd.DataFrame
        Dataframe consisting of the required data to build FE
        models.
    """
    df = self.set_df_structure(idx)
    df["height"] = pd.to_numeric(df["height"])
    df["wall_thickness"] = pd.to_numeric(df["wall_thickness"])
    df.rename(columns={"wall_thickness": "Wall thickness [mm]"}, inplace=True)
    df.rename(columns={"volume": "Volume [m3]"}, inplace=True)
    d_to = [d.split("/", 1)[0] for d in df["OD"].values]
    d_from = [d.split("/", 1)[1] if len(d.split("/", 1)) > 1 else d.split("/", 1)[0] for d in df["OD"].values]
    df["Diameter from [m]"] = np.array(d_from, dtype=float) * 1e-3
    df["Diameter to [m]"] = np.array(d_to, dtype=float) * 1e-3
    df["rho [t/m]"] = df["mass"] / df["height"]
    df["Mass [t]"] = df["mass"] * 1e-3
    df["Height [m]"] = df["height"] * 1e-3
    df["Youngs modulus [GPa]"] = 210
    df["Poissons ratio [-]"] = 0.3
    cols = [
        "Elevation from [mLAT]",
        "Elevation to [mLAT]",
        "Height [m]",
        "Diameter from [m]",
        "Diameter to [m]",
        "Volume [m3]",
        "Wall thickness [mm]",
        "Youngs modulus [GPa]",
        "Poissons ratio [-]",
        "Mass [t]",
        "rho [t/m]",
    ]
    return df.loc[:, cols].copy()
process_rna
process_rna()

Set dataframe with required properties to model the RNA system.

Raises:

Type Description
ValueError

If tower subassembly data not found.

Source code in src/owi/metadatabase/geometry/processing.py
def process_rna(self) -> None:
    """
    Set dataframe with required properties to model the RNA system.

    Raises
    ------
    ValueError
        If tower subassembly data not found.
    """
    if self.tw_sub_assemblies is None:
        raise ValueError("Tower subassembly data not found.")
    rna_index = self.tw_sub_assemblies.index.str.contains("RNA")
    rna = deepcopy(
        self.tw_sub_assemblies.loc[
            rna_index,
            ["mass", "moment_of_inertia", "x", "y", "z", "description"],
        ]
    )
    mi = rna["moment_of_inertia"].values
    i_xx, i_yy, i_zz = [], [], []
    for m in mi:
        i_xx.append(m["x"] * 1e-3)
        i_yy.append(m["y"] * 1e-3)
        i_zz.append(m["z"] * 1e-3)
    rna["Ixx [tm2]"] = i_xx
    rna["Iyy [tm2]"] = i_yy
    rna["Izz [tm2]"] = i_zz
    rna["Mass [t]"] = rna["mass"] * 1e-3
    rna["X [m]"] = rna["x"] * 1e-3
    rna["Y [m]"] = rna["y"] * 1e-3
    rna["Z [mLAT]"] = self.tower_base + rna["z"] * 1e-3
    rna.rename(columns={"description": "Description"}, inplace=True)
    cols = [
        "X [m]",
        "Y [m]",
        "Z [mLAT]",
        "Mass [t]",
        "Ixx [tm2]",
        "Iyy [tm2]",
        "Izz [tm2]",
        "Description",
    ]
    self.rna = rna[cols]
set_df_appurtenances
set_df_appurtenances(idx)

Set dataframe with required properties for concentrated masses.

Sets dataframe containing the required properties to model concentrated masses from database subassemblies.

Parameters:

Name Type Description Default
idx str

Index to identify corresponding subassembly with possible values: 'TW', 'TP', 'MP'.

required

Returns:

Type Description
DataFrame

Dataframe containing lumped masses data from database with Z coordinates in mLAT system.

Raises:

Type Description
ValueError

If subassembly data not found or unknown index.

Source code in src/owi/metadatabase/geometry/processing.py
def set_df_appurtenances(self, idx: str) -> pd.DataFrame:
    """
    Set dataframe with required properties for concentrated masses.

    Sets dataframe containing the required properties to model
    concentrated masses from database subassemblies.

    Parameters
    ----------
    idx : str
        Index to identify corresponding subassembly with possible
        values: 'TW', 'TP', 'MP'.

    Returns
    -------
    pd.DataFrame
        Dataframe containing lumped masses data from database with
        Z coordinates in mLAT system.

    Raises
    ------
    ValueError
        If subassembly data not found or unknown index.
    """
    cols = ["mass", "x", "y", "z", "description"]
    if idx == "TW":
        if self.tw_sub_assemblies is None:
            raise ValueError("Tower subassembly data not found.")
        df_index = self.tw_sub_assemblies.index.str.contains(idx)
        df = deepcopy(self.tw_sub_assemblies.loc[df_index, cols])
        df["Z [mLAT]"] = self.tower_base + df["z"] * 1e-3
    elif idx == "TP":
        if self.tp_sub_assemblies is None:
            raise ValueError("Transition piece subassembly data not found.")
        df_index = self.tp_sub_assemblies.index.str.contains(idx)
        df = deepcopy(self.tp_sub_assemblies.loc[df_index, cols + ["height"]])
        # Lumped masses have 'None' height whereas distributed masses present not 'None' values
        df["height"] = pd.to_numeric(df["height"])
        df = df[df["height"].isnull()]
        bottom = self.sub_assemblies["TP"].position.z * 1e-3  # m
        df["Z [mLAT]"] = bottom + df["z"] * 1e-3  # m
    elif idx == "MP":
        if self.mp_sub_assemblies is None:
            raise ValueError("Monopile subassembly data not found.")
        df_index = self.mp_sub_assemblies.index.str.contains(idx)
        df = deepcopy(self.mp_sub_assemblies.loc[df_index, cols + ["height"]])
        # Lumped masses have 'None' height whereas distributed masses present not 'None' values
        df["height"] = pd.to_numeric(df["height"])
        df = df[df["height"].isnull()]
        bottom = self.pile_toe
        df["Z [mLAT]"] = bottom + df["z"] * 1e-3
    else:
        raise ValueError("Unknown index.")
    return df
process_lumped_masses
process_lumped_masses(idx)

Create dataframe with required properties for lumped masses.

Creates dataframe containing the required properties to model lumped mass appurtenances. Note that if the preprocessor package does not find any appurtenances it'll return an empty dataframe.

Parameters:

Name Type Description Default
idx str

Index to identify corresponding subassembly with possible values: 'TW', 'TP', 'MP'.

required

Returns:

Type Description
DataFrame

Dataframe with lumped mass properties.

Source code in src/owi/metadatabase/geometry/processing.py
def process_lumped_masses(self, idx: str) -> pd.DataFrame:
    """
    Create dataframe with required properties for lumped masses.

    Creates dataframe containing the required properties to model
    lumped mass appurtenances. Note that if the preprocessor
    package does not find any appurtenances it'll return an empty
    dataframe.

    Parameters
    ----------
    idx : str
        Index to identify corresponding subassembly with possible
        values: 'TW', 'TP', 'MP'.

    Returns
    -------
    pd.DataFrame
        Dataframe with lumped mass properties.
    """
    df = self.set_df_appurtenances(idx)
    df["Mass [t]"] = df.mass * 1e-3
    df["X [m]"] = df.x * 1e-3
    df["Y [m]"] = df.y * 1e-3
    df.rename(columns={"description": "Description"}, inplace=True)
    cols = ["X [m]", "Y [m]", "Z [mLAT]", "Mass [t]", "Description"]
    return df.loc[:, cols].copy()
set_df_distributed_appurtenances
set_df_distributed_appurtenances(idx)

Set dataframe with required properties for distributed masses.

Sets dataframe containing the required properties to model distributed lumped masses from database.

Parameters:

Name Type Description Default
idx str

Index to identify corresponding subassembly with possible values: 'TW', 'TP', 'MP'.

required

Returns:

Type Description
DataFrame

Dataframe containing distributed lumped masses data from database. Z coordinates in mLAT system.

Raises:

Type Description
ValueError

If subassembly data not found or unknown index or distributed lumped masses located outside the transition piece.

Source code in src/owi/metadatabase/geometry/processing.py
def set_df_distributed_appurtenances(self, idx: str) -> pd.DataFrame:
    """
    Set dataframe with required properties for distributed masses.

    Sets dataframe containing the required properties to model
    distributed lumped masses from database.

    Parameters
    ----------
    idx : str
        Index to identify corresponding subassembly with possible
        values: 'TW', 'TP', 'MP'.

    Returns
    -------
    pd.DataFrame
        Dataframe containing distributed lumped masses data from
        database. Z coordinates in mLAT system.

    Raises
    ------
    ValueError
        If subassembly data not found or unknown index or
        distributed lumped masses located outside the transition
        piece.
    """
    cols = ["mass", "x", "y", "z", "height", "volume", "description"]
    if idx == "TP":
        if self.tp_sub_assemblies is None:
            raise ValueError("Transition piece subassembly data not found.")
        df_index = self.tp_sub_assemblies.index.str.contains(idx)
        df = deepcopy(self.tp_sub_assemblies.loc[df_index, cols])
        # Lumped masses have 'None' height whereas distributed masses present not 'None' values
        df["height"] = pd.to_numeric(df["height"])
        df = df[df["height"].notnull()]
        bottom_tp = self.tower_base - self.tp_sub_assemblies.iloc[0]["z"] * 1e-3
        df["Z [mLAT]"] = bottom_tp + df["z"] * 1e-3
    elif idx == "MP":
        if self.mp_sub_assemblies is None:
            raise ValueError("Monopile subassembly data not found.")
        df_index = self.mp_sub_assemblies.index.str.contains(idx)
        df = deepcopy(self.mp_sub_assemblies.loc[df_index, cols])
        # Lumped masses have 'None' height whereas distributed masses present not 'None' values
        df["height"] = pd.to_numeric(df["height"])
        df = df[df["height"].notnull()]
        bottom = self.pile_toe
        df["Z [mLAT]"] = bottom + df["z"] * 1e-3
    elif idx == "grout":
        if self.tp_sub_assemblies is None:
            raise ValueError("Transition piece subassembly data not found.")
        df_index = self.tp_sub_assemblies.index.str.contains(idx)
        df = deepcopy(self.tp_sub_assemblies.loc[df_index, cols])
        # Lumped masses have 'None' height whereas distributed masses present not 'None' values
        df["height"] = pd.to_numeric(df["height"])
        df = df[df["height"].notnull()]
        bottom_tp = self.tower_base - self.tp_sub_assemblies.iloc[0]["z"] * 1e-3
        df["Z [mLAT]"] = bottom_tp + df["z"] * 1e-3
    else:
        raise ValueError("Unknown index or non distributed lumped masses located outside the transition piece.")
    return df
process_distributed_lumped_masses
process_distributed_lumped_masses(idx)

Create dataframe with uniformly distributed appurtenances.

Creates dataframe containing the required properties to model uniformly distributed appurtenances. Note that if the preprocessor package does not find any appurtenances it'll return an empty dataframe.

Parameters:

Name Type Description Default
idx str

Index to identify corresponding subassembly with possible values: 'TP', 'MP'.

required

Returns:

Type Description
DataFrame

Dataframe with distributed lumped mass properties.

Source code in src/owi/metadatabase/geometry/processing.py
def process_distributed_lumped_masses(self, idx: str) -> pd.DataFrame:
    """
    Create dataframe with uniformly distributed appurtenances.

    Creates dataframe containing the required properties to model
    uniformly distributed appurtenances. Note that if the
    preprocessor package does not find any appurtenances it'll
    return an empty dataframe.

    Parameters
    ----------
    idx : str
        Index to identify corresponding subassembly with possible
        values: 'TP', 'MP'.

    Returns
    -------
    pd.DataFrame
        Dataframe with distributed lumped mass properties.
    """
    df = self.set_df_distributed_appurtenances(idx)
    df["Mass [t]"] = df["mass"] * 1e-3
    df["X [m]"] = df["x"] * 1e-3
    df["Y [m]"] = df["y"] * 1e-3
    df["Height [m]"] = df["height"] * 1e-3
    df.rename(columns={"volume": "Volume [m3]"}, inplace=True)
    df.rename(columns={"description": "Description"}, inplace=True)
    cols = [
        "X [m]",
        "Y [m]",
        "Z [mLAT]",
        "Height [m]",
        "Mass [t]",
        "Volume [m3]",
        "Description",
    ]
    return df.loc[:, cols].copy()
process_structure
process_structure(option='full')

Set dataframe with required properties to model the tower.

Sets dataframe containing the required properties to model the tower geometry, including the RNA system.

Parameters:

Name Type Description Default
option str

Option to process the data for a specific subassembly. Possible values:

  • "full": To process all the data for all subassemblies.
  • "tower": To process only the data for the tower subassembly.
  • "TP": To process only the data for the transition piece subassembly.
  • "monopile": To process only the data for the monopile foundation subassembly.
'full'

Examples:

>>> from contextlib import ExitStack
>>> from unittest import mock
>>> location = pd.DataFrame({"elevation": [30.0]})
>>> def _set_subassemblies(self, subassemblies):
...     self.sub_assemblies = {}
>>> def _set_members(self):
...     return None
>>> with mock.patch.object(
...     OWT,
...     "_set_subassemblies",
...     _set_subassemblies,
... ), mock.patch.object(OWT, "_set_members", _set_members):
...     owt = OWT(
...         api=object(),
...         materials=pd.DataFrame(),
...         subassemblies=pd.DataFrame(),
...         location=location,
...     )
>>> empty_df = pd.DataFrame()
>>> with ExitStack() as stack:
...     _ = stack.enter_context(mock.patch.object(OWT, "process_rna"))
...     _ = stack.enter_context(
...         mock.patch.object(
...             OWT,
...             "process_structure_geometry",
...             return_value=empty_df,
...         )
...     )
...     _ = stack.enter_context(
...         mock.patch.object(
...             OWT,
...             "process_lumped_masses",
...             return_value=empty_df,
...         )
...     )
...     _ = stack.enter_context(
...         mock.patch.object(
...             OWT,
...             "process_distributed_lumped_masses",
...             return_value=empty_df,
...         )
...     )
...     owt.process_structure(option="TW")
>>> owt._init_proc
True
Source code in src/owi/metadatabase/geometry/processing.py
def process_structure(self, option="full") -> None:
    """
    Set dataframe with required properties to model the tower.

    Sets dataframe containing the required properties to model the
    tower geometry, including the RNA system.

    Parameters
    ----------
    option : str, optional
        Option to process the data for a specific subassembly.
        Possible values:

        - "full": To process all the data for all subassemblies.
        - "tower": To process only the data for the tower
          subassembly.
        - "TP": To process only the data for the transition piece
          subassembly.
        - "monopile": To process only the data for the monopile
          foundation subassembly.

    Examples
    --------
    >>> from contextlib import ExitStack
    >>> from unittest import mock
    >>> location = pd.DataFrame({"elevation": [30.0]})
    >>> def _set_subassemblies(self, subassemblies):
    ...     self.sub_assemblies = {}
    >>> def _set_members(self):
    ...     return None
    >>> with mock.patch.object(
    ...     OWT,
    ...     "_set_subassemblies",
    ...     _set_subassemblies,
    ... ), mock.patch.object(OWT, "_set_members", _set_members):
    ...     owt = OWT(
    ...         api=object(),
    ...         materials=pd.DataFrame(),
    ...         subassemblies=pd.DataFrame(),
    ...         location=location,
    ...     )
    >>> empty_df = pd.DataFrame()
    >>> with ExitStack() as stack:
    ...     _ = stack.enter_context(mock.patch.object(OWT, "process_rna"))
    ...     _ = stack.enter_context(
    ...         mock.patch.object(
    ...             OWT,
    ...             "process_structure_geometry",
    ...             return_value=empty_df,
    ...         )
    ...     )
    ...     _ = stack.enter_context(
    ...         mock.patch.object(
    ...             OWT,
    ...             "process_lumped_masses",
    ...             return_value=empty_df,
    ...         )
    ...     )
    ...     _ = stack.enter_context(
    ...         mock.patch.object(
    ...             OWT,
    ...             "process_distributed_lumped_masses",
    ...             return_value=empty_df,
    ...         )
    ...     )
    ...     owt.process_structure(option="TW")
    >>> owt._init_proc
    True
    """
    self._init_proc = True
    if option == "full":
        self.process_rna()
        self.tower = self.process_structure_geometry("tw")
        self.transition_piece = self.process_structure_geometry("tp")
        self.monopile = self.process_structure_geometry("mp")
        self.tw_lumped_mass = self.process_lumped_masses("TW")
        self.tp_lumped_mass = self.process_lumped_masses("TP")
        self.mp_lumped_mass = self.process_lumped_masses("MP")
        self.tp_distributed_mass = self.process_distributed_lumped_masses("TP")
        self.mp_distributed_mass = self.process_distributed_lumped_masses("MP")
        self.grout = self.process_distributed_lumped_masses("grout")
    elif option == "TW":
        self.process_rna()
        self.tower = self.process_structure_geometry("tw")
        self.tw_lumped_mass = self.process_lumped_masses("TW")
    elif option == "TP":
        self.transition_piece = self.process_structure_geometry("tp")
        self.tp_lumped_mass = self.process_lumped_masses("TP")
        self.tp_distributed_mass = self.process_distributed_lumped_masses("TP")
        self.grout = self.process_distributed_lumped_masses("grout")
    elif option == "MP":
        self.monopile = self.process_structure_geometry("mp")
        self.mp_lumped_mass = self.process_lumped_masses("MP")
        self.mp_distributed_mass = self.process_distributed_lumped_masses("MP")
can_adjust_properties staticmethod
can_adjust_properties(row)

Recalculate can properties based on section and elevations.

Recalculation of can properties based on section properties and can elevations: height [m], volume [m3], mass [t], rho [t/m].

Parameters:

Name Type Description Default
row Series

Original can properties.

required

Returns:

Type Description
Series

Pandas series of recalculated can properties.

Examples:

>>> row = pd.Series(
...     {
...         "Mass [t]": 10.0,
...         "Volume [m3]": 5.0,
...         "Elevation from [mLAT]": 10.0,
...         "Elevation to [mLAT]": 0.0,
...         "Diameter from [m]": 6.0,
...         "Diameter to [m]": 6.0,
...         "Wall thickness [mm]": 10.0,
...     }
... )
>>> out = OWT.can_adjust_properties(row)
>>> float(out["Height [m]"])
10.0
Source code in src/owi/metadatabase/geometry/processing.py
@staticmethod
def can_adjust_properties(row: pd.Series) -> pd.Series:
    """
    Recalculate can properties based on section and elevations.

    Recalculation of can properties based on section properties and
    can elevations: height [m], volume [m3], mass [t], rho [t/m].

    Parameters
    ----------
    row : pd.Series
        Original can properties.

    Returns
    -------
    pd.Series
        Pandas series of recalculated can properties.

    Examples
    --------
    >>> row = pd.Series(
    ...     {
    ...         "Mass [t]": 10.0,
    ...         "Volume [m3]": 5.0,
    ...         "Elevation from [mLAT]": 10.0,
    ...         "Elevation to [mLAT]": 0.0,
    ...         "Diameter from [m]": 6.0,
    ...         "Diameter to [m]": 6.0,
    ...         "Wall thickness [mm]": 10.0,
    ...     }
    ... )
    >>> out = OWT.can_adjust_properties(row)
    >>> float(out["Height [m]"])
    10.0
    """
    density = row["Mass [t]"] / row["Volume [m3]"]
    height = row["Elevation from [mLAT]"] - row["Elevation to [mLAT]"]
    r1 = row["Diameter from [m]"] / 2
    r2 = row["Diameter to [m]"] / 2
    volume_out = 1 / 3 * np.pi * (r1**2 + r1 * r2 + r2**2) * height
    wall_thickness = row["Wall thickness [mm]"] * 1e-3
    r1 = r1 - wall_thickness
    r2 = r2 - wall_thickness
    volume_in = 1 / 3 * np.pi * (r1**2 + r1 * r2 + r2**2) * height
    volume = volume_out - volume_in
    mass = volume * density
    rho_m = mass / height
    can_properties = pd.Series(
        data=[height, volume, mass, rho_m],
        index=["Height [m]", "Volume [m3]", "Mass [t]", "rho [t/m]"],
    )
    return can_properties
can_modification
can_modification(df, altitude, position='bottom')

Change can properties based on the altitude.

Parameters:

Name Type Description Default
df DataFrame

Dataframe containing the can properties.

required
altitude float64 or None

Altitude in mLAT.

required
position str

Position of the can with respect to the altitude with possible values: "bottom" or "top", default is "bottom".

'bottom'

Returns:

Type Description
DataFrame

Dataframe with the modified can properties.

Examples:

>>> df = pd.DataFrame(
...     {
...         "Elevation from [mLAT]": [10.0],
...         "Elevation to [mLAT]": [0.0],
...         "Diameter from [m]": [6.0],
...         "Diameter to [m]": [6.0],
...         "Wall thickness [mm]": [10.0],
...         "Volume [m3]": [5.0],
...         "Mass [t]": [10.0],
...         "rho [t/m]": [1.0],
...     },
...     index=["A"],
... )
>>> from types import SimpleNamespace
>>> helper = SimpleNamespace(can_adjust_properties=OWT.can_adjust_properties)
>>> out = OWT.can_modification(helper, df.copy(), np.float64(5.0))
>>> float(out["Elevation to [mLAT]"].iloc[0])
5.0
Source code in src/owi/metadatabase/geometry/processing.py
def can_modification(
    self,
    df: pd.DataFrame,
    altitude: Union[np.float64, float, None],
    position: str = "bottom",
) -> pd.DataFrame:
    """
    Change can properties based on the altitude.

    Parameters
    ----------
    df : pd.DataFrame
        Dataframe containing the can properties.
    altitude : np.float64 or None
        Altitude in mLAT.
    position : str, optional
        Position of the can with respect to the altitude with
        possible values: "bottom" or "top", default is "bottom".

    Returns
    -------
    pd.DataFrame
        Dataframe with the modified can properties.

    Examples
    --------
    >>> df = pd.DataFrame(
    ...     {
    ...         "Elevation from [mLAT]": [10.0],
    ...         "Elevation to [mLAT]": [0.0],
    ...         "Diameter from [m]": [6.0],
    ...         "Diameter to [m]": [6.0],
    ...         "Wall thickness [mm]": [10.0],
    ...         "Volume [m3]": [5.0],
    ...         "Mass [t]": [10.0],
    ...         "rho [t/m]": [1.0],
    ...     },
    ...     index=["A"],
    ... )
    >>> from types import SimpleNamespace
    >>> helper = SimpleNamespace(can_adjust_properties=OWT.can_adjust_properties)
    >>> out = OWT.can_modification(helper, df.copy(), np.float64(5.0))
    >>> float(out["Elevation to [mLAT]"].iloc[0])
    5.0
    """
    if position == "bottom":
        ind = -1
        _col = " to "
    else:
        ind = 0
        _col = " from "
    altitude_val = float(altitude) if altitude is not None else float("nan")
    row_index = df.index[ind]
    df.loc[row_index, "Elevation" + _col + "[mLAT]"] = altitude_val
    col_elev_from = df.columns.get_loc("Elevation from [mLAT]")
    col_elev_to = df.columns.get_loc("Elevation to [mLAT]")
    col_diam_from = df.columns.get_loc("Diameter from [m]")
    col_diam_to = df.columns.get_loc("Diameter to [m]")
    if not isinstance(col_elev_from, int):
        raise ValueError("Expected scalar columns for elevation data.")
    if not isinstance(col_elev_to, int):
        raise ValueError("Expected scalar columns for elevation data.")
    if not isinstance(col_diam_from, int):
        raise ValueError("Expected scalar columns for diameter data.")
    if not isinstance(col_diam_to, int):
        raise ValueError("Expected scalar columns for diameter data.")
    elevation = [
        float(cast(float, df.iat[ind, col_elev_from])),
        float(cast(float, df.iat[ind, col_elev_to])),
    ]
    diameters = [
        float(cast(float, df.iat[ind, col_diam_from])),
        float(cast(float, df.iat[ind, col_diam_to])),
    ]
    df.loc[row_index, "Diameter" + _col + "[m]"] = float(
        np.interp(
            altitude_val,
            elevation,
            diameters,
        )
    )
    cols = ["Height [m]", "Volume [m3]", "Mass [t]", "rho [t/m]"]
    df.loc[df.index[ind], cols] = self.can_adjust_properties(df.iloc[ind])
    return df
assembly_tp_mp
assembly_tp_mp()

Process TP structural item to assembly with MP foundation.

Processes TP structural item to assembly with MP foundation ensuring continuity. TP skirt is processed as well.

Raises:

Type Description
TypeError

If TP or MP items need to be processed before.

Examples:

>>> from types import SimpleNamespace
>>> import pandas as pd
>>> helper = SimpleNamespace(
...     transition_piece=None,
...     monopile=None,
...     _init_spec_part=False,
... )
>>> OWT.assembly_tp_mp(helper)
Traceback (most recent call last):
    ...
TypeError: TP or MP items need to be processed before!
>>> tp = pd.DataFrame(
...     {
...         "Elevation from [mLAT]": [6.0, 0.0],
...         "Elevation to [mLAT]": [8.0, 4.0],
...         "Diameter from [m]": [6.0, 6.0],
...         "Diameter to [m]": [6.0, 6.0],
...         "Wall thickness [mm]": [10.0, 10.0],
...         "Volume [m3]": [5.0, 5.0],
...         "Mass [t]": [10.0, 10.0],
...         "rho [t/m]": [1.0, 1.0],
...     }
... )
>>> mp = pd.DataFrame(
...     {
...         "Elevation from [mLAT]": [0.0],
...         "Elevation to [mLAT]": [-10.0],
...         "Diameter from [m]": [6.0],
...         "Diameter to [m]": [6.0],
...         "Wall thickness [mm]": [10.0],
...         "Volume [m3]": [5.0],
...         "Mass [t]": [10.0],
...         "rho [t/m]": [1.0],
...     }
... )
>>> helper = SimpleNamespace(
...     transition_piece=tp,
...     monopile=mp,
...     pile_head=5.0,
...     substructure=None,
...     tp_skirt=None,
...     _init_spec_part=False,
... )
>>> helper.can_adjust_properties = OWT.can_adjust_properties
>>> helper.can_modification = lambda df, altitude, position="bottom": OWT.can_modification(
...     helper,
...     df,
...     altitude,
...     position=position,
... )
>>> OWT.assembly_tp_mp(helper)
>>> helper.substructure is not None
True
>>> helper.tp_skirt is not None
True
Source code in src/owi/metadatabase/geometry/processing.py
def assembly_tp_mp(self) -> None:
    """
    Process TP structural item to assembly with MP foundation.

    Processes TP structural item to assembly with MP foundation
    ensuring continuity. TP skirt is processed as well.

    Raises
    ------
    TypeError
        If TP or MP items need to be processed before.

    Examples
    --------
    >>> from types import SimpleNamespace
    >>> import pandas as pd
    >>> helper = SimpleNamespace(
    ...     transition_piece=None,
    ...     monopile=None,
    ...     _init_spec_part=False,
    ... )
    >>> OWT.assembly_tp_mp(helper)
    Traceback (most recent call last):
        ...
    TypeError: TP or MP items need to be processed before!
    >>> tp = pd.DataFrame(
    ...     {
    ...         "Elevation from [mLAT]": [6.0, 0.0],
    ...         "Elevation to [mLAT]": [8.0, 4.0],
    ...         "Diameter from [m]": [6.0, 6.0],
    ...         "Diameter to [m]": [6.0, 6.0],
    ...         "Wall thickness [mm]": [10.0, 10.0],
    ...         "Volume [m3]": [5.0, 5.0],
    ...         "Mass [t]": [10.0, 10.0],
    ...         "rho [t/m]": [1.0, 1.0],
    ...     }
    ... )
    >>> mp = pd.DataFrame(
    ...     {
    ...         "Elevation from [mLAT]": [0.0],
    ...         "Elevation to [mLAT]": [-10.0],
    ...         "Diameter from [m]": [6.0],
    ...         "Diameter to [m]": [6.0],
    ...         "Wall thickness [mm]": [10.0],
    ...         "Volume [m3]": [5.0],
    ...         "Mass [t]": [10.0],
    ...         "rho [t/m]": [1.0],
    ...     }
    ... )
    >>> helper = SimpleNamespace(
    ...     transition_piece=tp,
    ...     monopile=mp,
    ...     pile_head=5.0,
    ...     substructure=None,
    ...     tp_skirt=None,
    ...     _init_spec_part=False,
    ... )
    >>> helper.can_adjust_properties = OWT.can_adjust_properties
    >>> helper.can_modification = lambda df, altitude, position="bottom": OWT.can_modification(
    ...     helper,
    ...     df,
    ...     altitude,
    ...     position=position,
    ... )
    >>> OWT.assembly_tp_mp(helper)
    >>> helper.substructure is not None
    True
    >>> helper.tp_skirt is not None
    True
    """
    self._init_spec_part = True
    if (self.transition_piece is not None) and (self.monopile is not None):
        mp_head = self.pile_head
        tp = self.transition_piece
        df = deepcopy(tp.loc[tp["Elevation from [mLAT]"] > mp_head, :])
        if df.loc[df.index[0], "Elevation to [mLAT]"] != mp_head:
            # Not bolted connection (i.e. Rentel) preprocessing needed
            tp1 = self.can_modification(df, mp_head, position="bottom")
            self.substructure = pd.concat([tp1, deepcopy(self.monopile)])
        else:
            # Bolted connection, nothing to do
            self.substructure = pd.concat([df, deepcopy(self.monopile)])
        df = deepcopy(tp.loc[tp["Elevation to [mLAT]"] < mp_head, :])
        self.tp_skirt = self.can_modification(df, mp_head, position="top")
    else:
        raise TypeError("TP or MP items need to be processed before!")
assembly_full_structure
assembly_full_structure()

Process the full structure of the OWT.

Processes the full structure of the OWT: tower + tp combination with monopile.

Raises:

Type Description
TypeError

If tower or substructure needs to be processed before.

Examples:

>>> import pandas as pd
>>> from types import SimpleNamespace
>>> helper = SimpleNamespace(
...     substructure=pd.DataFrame({"Height [m]": [1.0]}),
...     tower=pd.DataFrame({"Height [m]": [2.0]}),
...     _init_spec_full=False,
... )
>>> OWT.assembly_full_structure(helper)
>>> float(helper.full_structure["Height [m]"].sum())
3.0
>>> helper._init_spec_full
True
>>> helper = SimpleNamespace(
...     substructure=None,
...     tower=None,
...     _init_spec_full=False,
... )
>>> OWT.assembly_full_structure(helper)
Traceback (most recent call last):
    ...
TypeError: Substructure needs to be processed before!
>>> helper = SimpleNamespace(
...     substructure=pd.DataFrame({"Height [m]": [1.0]}),
...     tower=None,
...     _init_spec_full=False,
... )
>>> OWT.assembly_full_structure(helper)
Traceback (most recent call last):
    ...
TypeError: Tower needs to be processed before!
Source code in src/owi/metadatabase/geometry/processing.py
def assembly_full_structure(self) -> None:
    """
    Process the full structure of the OWT.

    Processes the full structure of the OWT: tower + tp combination
    with monopile.

    Raises
    ------
    TypeError
        If tower or substructure needs to be processed before.

    Examples
    --------
    >>> import pandas as pd
    >>> from types import SimpleNamespace
    >>> helper = SimpleNamespace(
    ...     substructure=pd.DataFrame({"Height [m]": [1.0]}),
    ...     tower=pd.DataFrame({"Height [m]": [2.0]}),
    ...     _init_spec_full=False,
    ... )
    >>> OWT.assembly_full_structure(helper)
    >>> float(helper.full_structure["Height [m]"].sum())
    3.0
    >>> helper._init_spec_full
    True
    >>> helper = SimpleNamespace(
    ...     substructure=None,
    ...     tower=None,
    ...     _init_spec_full=False,
    ... )
    >>> OWT.assembly_full_structure(helper)
    Traceback (most recent call last):
        ...
    TypeError: Substructure needs to be processed before!
    >>> helper = SimpleNamespace(
    ...     substructure=pd.DataFrame({"Height [m]": [1.0]}),
    ...     tower=None,
    ...     _init_spec_full=False,
    ... )
    >>> OWT.assembly_full_structure(helper)
    Traceback (most recent call last):
        ...
    TypeError: Tower needs to be processed before!
    """
    self._init_spec_full = True
    if self.substructure is not None:
        if self.tower is not None:
            self.full_structure = pd.concat([self.tower, self.substructure])
        else:
            raise TypeError("Tower needs to be processed before!")
    else:
        raise TypeError("Substructure needs to be processed before!")
extend_dfs
extend_dfs()

Extend the dataframes with the subassembly columns.

Examples:

>>> import pandas as pd
>>> from types import SimpleNamespace
>>> helper = SimpleNamespace(
...     pile_toe=None,
...     rna=None,
...     tower=pd.DataFrame({"Height [m]": [1.0]}),
...     transition_piece=None,
...     monopile=None,
...     tw_lumped_mass=None,
...     tp_lumped_mass=None,
...     mp_lumped_mass=None,
...     tp_distributed_mass=None,
...     mp_distributed_mass=None,
...     grout=None,
...     sub_assemblies={},
...     substructure=None,
...     tp_skirt=None,
...     full_structure=None,
...     _init_spec_part=False,
...     _init_spec_full=False,
... )
>>> OWT.extend_dfs(helper)
>>> helper.tower["Subassembly"].iloc[0]
'TW'
>>> helper.tp_skirt is None
True
Source code in src/owi/metadatabase/geometry/processing.py
def extend_dfs(self) -> None:
    """
    Extend the dataframes with the subassembly columns.

    Examples
    --------
    >>> import pandas as pd
    >>> from types import SimpleNamespace
    >>> helper = SimpleNamespace(
    ...     pile_toe=None,
    ...     rna=None,
    ...     tower=pd.DataFrame({"Height [m]": [1.0]}),
    ...     transition_piece=None,
    ...     monopile=None,
    ...     tw_lumped_mass=None,
    ...     tp_lumped_mass=None,
    ...     mp_lumped_mass=None,
    ...     tp_distributed_mass=None,
    ...     mp_distributed_mass=None,
    ...     grout=None,
    ...     sub_assemblies={},
    ...     substructure=None,
    ...     tp_skirt=None,
    ...     full_structure=None,
    ...     _init_spec_part=False,
    ...     _init_spec_full=False,
    ... )
    >>> OWT.extend_dfs(helper)
    >>> helper.tower["Subassembly"].iloc[0]
    'TW'
    >>> helper.tp_skirt is None
    True
    """
    for attr in ATTR_PROC:
        df = getattr(self, attr)
        if df is not None:
            if "tower" in attr or "tw_" in attr or "rna" in attr:
                df["Subassembly"] = "TW"
                setattr(self, attr, df)
            elif "tp_" in attr or "transition" in attr or "grout" in attr:
                df["Subassembly"] = "TP"
                setattr(self, attr, df)
            elif "mp_" in attr or "monopile" in attr:
                df["Subassembly"] = "MP"
                setattr(self, attr, df)
    if "TP" in self.sub_assemblies and "MP" in self.sub_assemblies:
        self.assembly_tp_mp()
    else:
        self._init_spec_part = True
        self.tp_skirt = None
    if "TW" in self.sub_assemblies:
        self._init_spec_full = True
        if self.substructure is not None:
            self.assembly_full_structure()
        else:
            self.full_structure = None
    else:
        self.full_structure = None
        self._init_spec_full = True
transform_monopile_geometry
transform_monopile_geometry(cutoff_point=nan)

Return a dataframe with monopile geometry.

Returns a dataframe with the monopile geometry with the mudline as reference.

Parameters:

Name Type Description Default
cutoff_point floating

Depth from the mudline to cut the monopile geometry.

nan

Returns:

Type Description
DataFrame

Dataframe with the monopile geometry.

Raises:

Type Description
ValueError

If monopile subassembly data not found.

Source code in src/owi/metadatabase/geometry/processing.py
@typing.no_type_check
def transform_monopile_geometry(
    self,
    cutoff_point: np.floating = np.nan,
) -> pd.DataFrame:
    """
    Return a dataframe with monopile geometry.

    Returns a dataframe with the monopile geometry with the mudline
    as reference.

    Parameters
    ----------
    cutoff_point : np.floating, optional
        Depth from the mudline to cut the monopile geometry.

    Returns
    -------
    pd.DataFrame
        Dataframe with the monopile geometry.

    Raises
    ------
    ValueError
        If monopile subassembly data not found.
    """
    toe_depth_lat = self.sub_assemblies["MP"].position.z
    penetration = -((1e-3 * toe_depth_lat) - self.water_depth)
    pile = pd.DataFrame()
    if self.mp_sub_assemblies is not None:
        df = self.mp_sub_assemblies.copy()
    else:
        raise ValueError("Monopile subassembly data not found.")
    df.reset_index(inplace=True)
    for i, row in df.iterrows():
        if i != 0:
            pile.loc[i, "Elevation from [m]"] = penetration - 1e-3 * df["z"].iloc[i - 1]
            pile.loc[i, "Elevation to [m]"] = penetration - 1e-3 * row["z"]
            pile.loc[i, "Pile material"] = self.sub_assemblies["MP"].bb[0].material.title
            pile.loc[i, "Pile material submerged unit weight [kN/m3]"] = (
                1e-2 * self.sub_assemblies["MP"].bb[0].material.density - 10
            )
            pile.loc[i, "Wall thickness [mm]"] = row["wall_thickness"]
            bot_od = row["OD"].split("/")[0] if "/" in row["OD"] else row["OD"]
            top_od = row["OD"].split("/")[1] if "/" in row["OD"] else row["OD"]
            pile.loc[i, "Diameter [m]"] = 1e-3 * 0.5 * (float(bot_od) + float(top_od))
            pile.loc[i, "Youngs modulus [GPa]"] = self.sub_assemblies["MP"].bb[0].material.young_modulus
            pile.loc[i, "Poissons ratio [-]"] = self.sub_assemblies["MP"].bb[0].material.poisson_ratio
    if not np.isnan(cutoff_point):
        pile = pile.loc[pile["Elevation to [m]"] > cutoff_point].reset_index(drop=True)
        pile.loc[0, "Elevation from [m]"] = cutoff_point
    return pile

OWTs

OWTs(turbines, owts)

Class to process the geometry data of multiple OWTs.

:param owts: List of OWT objects. :param api: API object used to call get_* methods. :param materials: Pandas dataframe with the materials data. :param sub_assemblies: Dictionary of dictionaries of the subassemblies for each turbine. :param tower_base: Dictionary of the elevation of the OWT tower base in mLAT for each turbine. :param pile_head: Dictionary of the elevation of the pile head in mLAT for each turbine. :param water_depth: Dictionary of the water depth in mLAT for each turbine. :param tw_sub_assemblies: Dataframe of the tower subassemblies data from each turbine. :param tp_sub_assemblies: Dataframe of the transition piece subassemblies data from each turbine. :param mp_sub_assemblies: Dataframe of the monopile subassemblies data from each turbine. :param pile_toe: Dataframe of the elevation of the pile toe in mLAT from each turbine. :param rna: Dataframe of the RNA data from each turbine. :param tower: Dataframe of the tower data from each turbine. :param transition_piece: Dataframe of the transition piece data from each turbine. :param monopile: Dataframe of the monopile data from each turbine. :param tw_lumped_mass: Dataframe of the lumped masses data of the tower from each turbine. :param tp_lumped_mass: Dataframe of the lumped masses data of the transition piece from each turbine. :param mp_lumped_mass: Dataframe of the lumped masses data of the monopile from each turbine. :param tp_distributed_mass: Dataframe of the distributed masses data of the transition piece from each turbine. :param mp_distributed_mass: Dataframe of the distributed masses data of the monopile from each turbine. :param grout: Dataframe of the grout data from each turbine. :param full_structure: Dataframe of the full structure data from each turbine. :param tp_skirt: Dataframe of the transition piece skirt data from each turbine. :param substructure: Dataframe of the substructure data from each turbine. :param all_turbines: Dataframe of the general geometry data from each turbine. :param all_tubular_structures: Dataframe of the tubular structures data from each turbine. :param all_distributed_mass: Dataframe of the distributed masses data from each turbine. :param all_lumped_mass: Dataframe of the lumped masses data from each turbine.

Create an instance of the OWTs class with required parameters.

Parameters:

Name Type Description Default
turbines list of str

List of turbine titles.

required
owts list of OWT

List of OWT objects.

required

Examples:

>>> from types import SimpleNamespace
>>> stub = SimpleNamespace(
...     api="api",
...     materials="materials",
...     sub_assemblies={},
...     tower_base=0.0,
...     pile_head=0.0,
...     water_depth=0.0,
...     tw_sub_assemblies=None,
...     tp_sub_assemblies=None,
...     mp_sub_assemblies=None,
... )
>>> owts = OWTs(["T01"], [stub])
>>> owts.api
'api'
Source code in src/owi/metadatabase/geometry/processing.py
def __init__(
    self,
    turbines: list[str],
    owts: list[OWT],
) -> None:
    """
    Create an instance of the OWTs class with required parameters.

    Parameters
    ----------
    turbines : list of str
        List of turbine titles.
    owts : list of OWT
        List of OWT objects.

    Examples
    --------
    >>> from types import SimpleNamespace
    >>> stub = SimpleNamespace(
    ...     api="api",
    ...     materials="materials",
    ...     sub_assemblies={},
    ...     tower_base=0.0,
    ...     pile_head=0.0,
    ...     water_depth=0.0,
    ...     tw_sub_assemblies=None,
    ...     tp_sub_assemblies=None,
    ...     mp_sub_assemblies=None,
    ... )
    >>> owts = OWTs(["T01"], [stub])
    >>> owts.api
    'api'
    """
    self.owts = dict(zip(turbines, owts))
    self.api = self.owts[turbines[0]].api
    self.materials = self.owts[turbines[0]].materials
    for attr in [
        "sub_assemblies",
        "tower_base",
        "pile_head",
        "water_depth",
    ]:
        dict_ = {k: getattr(owt, attr) for k, owt in zip(turbines, self.owts.values())}
        setattr(self, attr, dict_)
    for attr in [
        "tw_sub_assemblies",
        "tp_sub_assemblies",
        "mp_sub_assemblies",
    ]:
        sa_turb_list = [getattr(owt, attr) for owt in self.owts.values() if getattr(owt, attr) is not None]
        df = None if sa_turb_list == [] else pd.concat(sa_turb_list)
        setattr(self, attr, df)
    for attr in ATTR_PROC:
        setattr(self, attr, [])
    for attr in ATTR_SPEC:
        setattr(self, attr, [])
    for attr in ATTR_FULL:
        setattr(self, attr, [])
    self._init = False
Functions
process_structures
process_structures()

Set dataframes with required properties to model the tower.

Sets dataframes containing the required properties to model the tower geometry, including the RNA system.

Examples:

>>> from types import SimpleNamespace
>>> from unittest import mock
>>> stub = SimpleNamespace(
...     api="api",
...     materials="materials",
...     sub_assemblies={"TW": 1, "TP": 1, "MP": 1},
...     tower_base=0.0,
...     pile_head=0.0,
...     water_depth=0.0,
...     tw_sub_assemblies=None,
...     tp_sub_assemblies=None,
...     mp_sub_assemblies=None,
...     process_structure=lambda *args, **kwargs: None,
...     extend_dfs=lambda *args, **kwargs: None,
...     pile_toe=0.0,
...     rna=None,
...     tower=None,
...     transition_piece=None,
...     monopile=None,
...     tw_lumped_mass=None,
...     tp_lumped_mass=None,
...     mp_lumped_mass=None,
...     tp_distributed_mass=None,
...     mp_distributed_mass=None,
...     grout=None,
...     full_structure=None,
...     tp_skirt=None,
...     substructure=None,
...     all_tubular_structures=None,
...     all_distributed_mass=None,
...     all_lumped_mass=None,
...     all_turbines=None,
... )
>>> owts = OWTs(["T01"], [stub])
>>> with mock.patch.object(OWTs, "_concat_list", lambda self, attrs: None), mock.patch.object(
...     OWTs, "_assembly_turbine", lambda self: None
... ):
...     owts.process_structures()
>>> owts._init
True
Source code in src/owi/metadatabase/geometry/processing.py
def process_structures(self) -> None:
    """
    Set dataframes with required properties to model the tower.

    Sets dataframes containing the required properties to model the
    tower geometry, including the RNA system.

    Examples
    --------
    >>> from types import SimpleNamespace
    >>> from unittest import mock
    >>> stub = SimpleNamespace(
    ...     api="api",
    ...     materials="materials",
    ...     sub_assemblies={"TW": 1, "TP": 1, "MP": 1},
    ...     tower_base=0.0,
    ...     pile_head=0.0,
    ...     water_depth=0.0,
    ...     tw_sub_assemblies=None,
    ...     tp_sub_assemblies=None,
    ...     mp_sub_assemblies=None,
    ...     process_structure=lambda *args, **kwargs: None,
    ...     extend_dfs=lambda *args, **kwargs: None,
    ...     pile_toe=0.0,
    ...     rna=None,
    ...     tower=None,
    ...     transition_piece=None,
    ...     monopile=None,
    ...     tw_lumped_mass=None,
    ...     tp_lumped_mass=None,
    ...     mp_lumped_mass=None,
    ...     tp_distributed_mass=None,
    ...     mp_distributed_mass=None,
    ...     grout=None,
    ...     full_structure=None,
    ...     tp_skirt=None,
    ...     substructure=None,
    ...     all_tubular_structures=None,
    ...     all_distributed_mass=None,
    ...     all_lumped_mass=None,
    ...     all_turbines=None,
    ... )
    >>> owts = OWTs(["T01"], [stub])
    >>> with mock.patch.object(OWTs, "_concat_list", lambda self, attrs: None), mock.patch.object(
    ...     OWTs, "_assembly_turbine", lambda self: None
    ... ):
    ...     owts.process_structures()
    >>> owts._init
    True
    """
    attr_list = ATTR_PROC + ATTR_SPEC + ATTR_FULL
    attr_list.remove("all_turbines")
    if self._init:
        return
    self._init = True
    for owt in self.owts.values():
        if len(owt.sub_assemblies) != 3:
            for sa in owt.sub_assemblies.keys():  # noqa: SIM118
                owt.process_structure(option=sa)
        else:
            owt.process_structure()
        owt.extend_dfs()
        for attr in attr_list:
            if attr == "pile_toe":
                pile_toe_list = cast(list[Union[np.float64, float, None]], self.pile_toe)
                pile_toe_list.append(getattr(owt, attr))
                self.pile_toe = pile_toe_list
            elif attr == "all_tubular_structures":
                self.all_tubular_structures.extend([owt.tower, owt.transition_piece, owt.monopile])
            elif attr == "all_distributed_mass":
                self.all_distributed_mass.extend(
                    [
                        owt.tp_distributed_mass,
                        owt.grout,
                        owt.mp_distributed_mass,
                    ]
                )
            elif attr == "all_lumped_mass":
                if isinstance(owt.rna, pd.DataFrame):
                    cols = [
                        "X [m]",
                        "Y [m]",
                        "Z [mLAT]",
                        "Mass [t]",
                        "Description",
                        "Subassembly",
                    ]
                    rna_ = owt.rna[cols]
                else:
                    rna_ = owt.rna
                self.all_lumped_mass.extend(
                    [
                        rna_,
                        owt.tw_lumped_mass,
                        owt.tp_lumped_mass,
                        owt.mp_lumped_mass,
                    ]
                )
            else:
                attr_val = getattr(self, attr)
                owt_attr_val = getattr(owt, attr)
                attr_val.append(owt_attr_val)
    attr_list.remove("pile_toe")
    self.pile_toe = dict(zip(self.owts.keys(), self.pile_toe))
    self._concat_list(attr_list)
    self._assembly_turbine()
select_owt
select_owt(turbine)

Select OWT object from the OWTs object.

Parameters:

Name Type Description Default
turbine str or int

Title of the turbine or its index in the original list of turbine titles (from get method).

required

Returns:

Type Description
OWT

OWT object.

Raises:

Type Description
ValueError

If turbine must be specified as single turbine title or its index from the get method input turbine list.

Examples:

>>> from types import SimpleNamespace
>>> stub = SimpleNamespace(
...     api="api",
...     materials="materials",
...     sub_assemblies={},
...     tower_base=0.0,
...     pile_head=0.0,
...     water_depth=0.0,
...     tw_sub_assemblies=None,
...     tp_sub_assemblies=None,
...     mp_sub_assemblies=None,
... )
>>> owts = OWTs(["T01"], [stub])
>>> owts.select_owt("T01") is stub
True
Source code in src/owi/metadatabase/geometry/processing.py
def select_owt(self, turbine: Union[str, int]) -> OWT:
    """
    Select OWT object from the OWTs object.

    Parameters
    ----------
    turbine : str or int
        Title of the turbine or its index in the original list of
        turbine titles (from get method).

    Returns
    -------
    OWT
        OWT object.

    Raises
    ------
    ValueError
        If turbine must be specified as single turbine title or
        its index from the get method input turbine list.

    Examples
    --------
    >>> from types import SimpleNamespace
    >>> stub = SimpleNamespace(
    ...     api="api",
    ...     materials="materials",
    ...     sub_assemblies={},
    ...     tower_base=0.0,
    ...     pile_head=0.0,
    ...     water_depth=0.0,
    ...     tw_sub_assemblies=None,
    ...     tp_sub_assemblies=None,
    ...     mp_sub_assemblies=None,
    ... )
    >>> owts = OWTs(["T01"], [stub])
    >>> owts.select_owt("T01") is stub
    True
    """
    if isinstance(turbine, int):
        return self.owts[list(self.owts.keys())[turbine]]
    elif isinstance(turbine, str):
        return self.owts[turbine]
    else:
        raise ValueError(
            "You must specify a single turbine title or \
            its index from the the get method input turbine list."
        )

geometry.io — API Client

io

Module to connect to the database API to retrieve and operate on geometry data.

Classes

GeometryAPI
GeometryAPI(api_subdir='/geometry/userroutes/', **kwargs)

Bases: API

Class to connect to the geometry data API with methods to retrieve data.

Create an instance of the GeometryAPI class with required parameters.

Parameters:

Name Type Description Default
api_subdir str

Subdirectory of the API endpoint url for specific type of data.

'/geometry/userroutes/'
**kwargs

Additional parameters to pass to the API (see the base class).

{}

Examples:

>>> api = GeometryAPI(
...     api_root="https://example",
...     header={"Authorization": "Token test"},
... )
>>> api.api_root.endswith("/geometry/userroutes/")
True
Source code in src/owi/metadatabase/geometry/io.py
def __init__(
    self,
    api_subdir: str = "/geometry/userroutes/",
    **kwargs,
) -> None:
    """
    Create an instance of the GeometryAPI class with required
    parameters.

    Parameters
    ----------
    api_subdir : str, optional
        Subdirectory of the API endpoint url for specific type of
        data.
    **kwargs
        Additional parameters to pass to the API (see the base
        class).

    Examples
    --------
    >>> api = GeometryAPI(
    ...     api_root="https://example",
    ...     header={"Authorization": "Token test"},
    ... )
    >>> api.api_root.endswith("/geometry/userroutes/")
    True
    """
    super().__init__(**kwargs)
    self.loc_api = LocationsAPI(**kwargs)
    self.api_root = self.api_root + api_subdir
Functions
get_model_definitions
get_model_definitions(projectsite=None)

Get all relevant model definitions.

Parameters:

Name Type Description Default
projectsite str

Title of the projectsite.

None

Returns:

Type Description
dict

Dictionary with the following keys:

  • "data": Pandas dataframe with the model definitions
  • "exists": Boolean indicating whether matching records are found

Examples:

    >>> from unittest import mock
    >>> api = GeometryAPI(
    ...     api_root="https://example",
    ...     header={"Authorization": "Token test"},
    ... )
    >>> df = pd.DataFrame({"id": [1]})
    >>> with mock.patch.object(
    ...     GeometryAPI,
    ...     "process_data",
    ...     return_value=(df, {"existance": True}),
    ... ):
    ...     out = api.get_model_definitions(projectsite="Site")
    >>> out["exists"]
    True
Source code in src/owi/metadatabase/geometry/io.py
def get_model_definitions(
    self,
    projectsite: Union[str, None] = None,
) -> dict[str, Union[pd.DataFrame, bool, np.int64, None]]:
    """
    Get all relevant model definitions.

    Parameters
    ----------
    projectsite : str, optional
        Title of the projectsite.

    Returns
    -------
    dict
        Dictionary with the following keys:

        - "data": Pandas dataframe with the model definitions
        - "exists": Boolean indicating whether matching records
          are found

    Examples
    --------
            >>> from unittest import mock
            >>> api = GeometryAPI(
            ...     api_root="https://example",
            ...     header={"Authorization": "Token test"},
            ... )
            >>> df = pd.DataFrame({"id": [1]})
            >>> with mock.patch.object(
            ...     GeometryAPI,
            ...     "process_data",
            ...     return_value=(df, {"existance": True}),
            ... ):
            ...     out = api.get_model_definitions(projectsite="Site")
            >>> out["exists"]
            True
    """
    url_params = {}
    if projectsite is not None:
        url_params["site"] = projectsite
    url_data_type = "modeldefinitions"
    output_type = "list"
    with self._temp_api_root(self.api_root.replace("userroutes", "routes")):
        df, df_add = self.process_data(url_data_type, url_params, output_type)
    return {"data": df, "exists": df_add["existance"]}
get_modeldefinition_id
get_modeldefinition_id(
    assetlocation=None,
    projectsite=None,
    model_definition=None,
)

Get the ID of a model definition.

Either the asset location or the project site must be specified.

Parameters:

Name Type Description Default
assetlocation str

Title of the asset location.

None
projectsite str

Title of the projectsite.

None
model_definition str

Title of the model definition.

None

Returns:

Type Description
dict

Dictionary with the following keys:

  • "id": ID of the specified model definition
  • "multiple_modeldef": Boolean indicating whether there are multiple model definitions for the asset location in general

Raises:

Type Description
ValueError

If at least one of assetlocation or projectsite is not specified, if no location found, if no model definitions found, if multiple model definitions found without specification, or if specified model definition not found.

Examples:

>>> api = GeometryAPI(
...     api_root="https://example",
...     header={"Authorization": "Token test"},
... )
>>> api.get_modeldefinition_id()
Traceback (most recent call last):
    ...
ValueError: At least either of the related ... must be specified!
Source code in src/owi/metadatabase/geometry/io.py
def get_modeldefinition_id(
    self,
    assetlocation: Union[str, None] = None,
    projectsite: Union[str, None] = None,
    model_definition: Union[str, None] = None,
) -> dict[str, Union[int, np.int64, bool, None]]:
    """
    Get the ID of a model definition.

    Either the asset location or the project site must be specified.

    Parameters
    ----------
    assetlocation : str, optional
        Title of the asset location.
    projectsite : str, optional
        Title of the projectsite.
    model_definition : str, optional
        Title of the model definition.

    Returns
    -------
    dict
        Dictionary with the following keys:

        - "id": ID of the specified model definition
        - "multiple_modeldef": Boolean indicating whether there
          are multiple model definitions for the asset location
          in general

    Raises
    ------
    ValueError
        If at least one of assetlocation or projectsite is not
        specified, if no location found, if no model definitions
        found, if multiple model definitions found without
        specification, or if specified model definition not found.

    Examples
    --------
    >>> api = GeometryAPI(
    ...     api_root="https://example",
    ...     header={"Authorization": "Token test"},
    ... )
    >>> api.get_modeldefinition_id()  # doctest: +ELLIPSIS
    Traceback (most recent call last):
        ...
    ValueError: At least either of the related ... must be specified!
    """
    if assetlocation is None and projectsite is None:
        raise ValueError("At least either of the related `assetlocation` or `projectsite` must be specified!")

    result = {}
    if projectsite is None:
        if assetlocation is None:
            raise ValueError("Asset location must be specified when projectsite is None.")
        location_data = self.loc_api.get_assetlocation_detail(assetlocation=assetlocation)
        if location_data["exists"]:
            location = cast(pd.DataFrame, location_data["data"])
        else:
            raise ValueError(f"No location found for asset {assetlocation}.")
        projectsite = location["projectsite_name"].loc[0]
    model_definitions_data = self.get_model_definitions(projectsite=projectsite)
    if model_definitions_data["exists"]:
        model_definitions = cast(pd.DataFrame, model_definitions_data["data"])
    else:
        raise ValueError(f"No model definitions found for project site {projectsite}.")
    if model_definition is None and len(model_definitions) > 1:
        raise ValueError(
            f"Multiple model definitions found for project site {projectsite}. Please specify which one to use."
        )
    if model_definition is None:
        model_definition_id = model_definitions["id"].values[0]
        result["id"] = model_definition_id
        result["multiple_modeldef"] = False
    else:
        matching_definitions = model_definitions[model_definitions["title"] == model_definition]
        if matching_definitions.empty:
            raise ValueError(f"Model definition '{model_definition}' not found for project site {projectsite}.")
        if len(matching_definitions) > 1:
            raise ValueError(
                f"Multiple model definitions found for '{model_definition}' in project site {projectsite}.\n"
                f"Please check the data consistency."
            )
        model_definition_id = matching_definitions["id"].values[0]
        result["id"] = model_definition_id
        result["multiple_modeldef"] = len(model_definitions) > 1
    return result
get_subassemblies
get_subassemblies(
    projectsite=None,
    assetlocation=None,
    subassembly_type=None,
    model_definition=None,
)

Get all relevant structure subassemblies.

If you specify a model definition, you also must specify either the projectsite or the asset location.

Parameters:

Name Type Description Default
projectsite str

Title of the projectsite.

None
assetlocation str

Title of the asset location.

None
subassembly_type str

Type of the subassembly.

None
model_definition str

Title of the model definition.

None

Returns:

Type Description
dict

Dictionary with the following keys:

  • "data": Pandas dataframe with the location data for each project
  • "exists": Boolean indicating whether matching records are found

Raises:

Type Description
ValueError

If model definition specified without projectsite or assetlocation, or if specified model definition not found.

Examples:

>>> from unittest import mock
>>> api = GeometryAPI(
...     api_root="https://example",
...     header={"Authorization": "Token test"},
... )
>>> df = pd.DataFrame({"id": [1]})
>>> with mock.patch.object(
...     GeometryAPI,
...     "process_data",
...     return_value=(df, {"existance": True}),
... ):
...     out = api.get_subassemblies(projectsite="Site")
>>> out["exists"]
True
Source code in src/owi/metadatabase/geometry/io.py
def get_subassemblies(
    self,
    projectsite: Union[str, None] = None,
    assetlocation: Union[str, None] = None,
    subassembly_type: Union[str, None] = None,
    model_definition: Union[str, None] = None,
) -> dict[str, Union[pd.DataFrame, bool, np.int64, None]]:
    """
    Get all relevant structure subassemblies.

    If you specify a model definition, you also must specify either
    the projectsite or the asset location.

    Parameters
    ----------
    projectsite : str, optional
        Title of the projectsite.
    assetlocation : str, optional
        Title of the asset location.
    subassembly_type : str, optional
        Type of the subassembly.
    model_definition : str, optional
        Title of the model definition.

    Returns
    -------
    dict
        Dictionary with the following keys:

        - "data": Pandas dataframe with the location data for each
          project
        - "exists": Boolean indicating whether matching records
          are found

    Raises
    ------
    ValueError
        If model definition specified without projectsite or
        assetlocation, or if specified model definition not found.

    Examples
    --------
    >>> from unittest import mock
    >>> api = GeometryAPI(
    ...     api_root="https://example",
    ...     header={"Authorization": "Token test"},
    ... )
    >>> df = pd.DataFrame({"id": [1]})
    >>> with mock.patch.object(
    ...     GeometryAPI,
    ...     "process_data",
    ...     return_value=(df, {"existance": True}),
    ... ):
    ...     out = api.get_subassemblies(projectsite="Site")
    >>> out["exists"]
    True
    """
    url_params = {}
    func_args = {}
    if projectsite is not None:
        url_params["asset__projectsite__title"] = projectsite
        func_args["projectsite"] = projectsite
    if assetlocation is not None:
        url_params["asset__title"] = assetlocation
        func_args["assetlocation"] = assetlocation
    if subassembly_type is not None:
        url_params["subassembly_type"] = subassembly_type
    if model_definition is not None:
        if projectsite is not None or assetlocation is not None:
            func_args["model_definition"] = model_definition
            modeldef_data = self.get_modeldefinition_id(**func_args)
            if modeldef_data["id"] is not None:
                url_params["model_definition"] = str(modeldef_data["id"])
            else:
                raise ValueError(
                    f"No model definition {model_definition} found for project site {projectsite} "
                    f"or asset location {assetlocation}."
                )
        else:
            raise ValueError(
                "If you specify a model definition, you also must specify either "
                "the projectsite or the asset location!"
            )
    url_data_type = "subassemblies"
    output_type = "list"
    df, df_add = self.process_data(url_data_type, url_params, output_type)
    return {"data": df, "exists": df_add["existance"]}
get_buildingblocks
get_buildingblocks(
    projectsite=None,
    assetlocation=None,
    subassembly_type=None,
    subassembly_id=None,
)

Get all relevant building blocks.

Parameters:

Name Type Description Default
projectsite str

Title of the projectsite.

None
assetlocation str

Title of the asset location.

None
subassembly_type str

Type of the subassemblies (e.g. 'MP', 'TW', 'TP').

None
subassembly_id int or int64

ID of the subassembly.

None

Returns:

Type Description
dict

Dictionary with the following keys:

  • "data": Pandas dataframe with the location data for each project
  • "exists": Boolean indicating whether matching records are found

Examples:

    >>> from unittest import mock
    >>> api = GeometryAPI(
    ...     api_root="https://example",
    ...     header={"Authorization": "Token test"},
    ... )
    >>> df = pd.DataFrame({"id": [1]})
    >>> with mock.patch.object(
    ...     GeometryAPI,
    ...     "process_data",
    ...     return_value=(df, {"existance": True}),
    ... ):
    ...     out = api.get_buildingblocks(projectsite="Site")
    >>> out["exists"]
    True
Source code in src/owi/metadatabase/geometry/io.py
def get_buildingblocks(
    self,
    projectsite: Union[str, None] = None,
    assetlocation: Union[str, None] = None,
    subassembly_type: Union[str, None] = None,
    subassembly_id: Union[int, np.int64, None] = None,
) -> dict[str, Union[pd.DataFrame, bool, np.int64, None]]:
    """
    Get all relevant building blocks.

    Parameters
    ----------
    projectsite : str, optional
        Title of the projectsite.
    assetlocation : str, optional
        Title of the asset location.
    subassembly_type : str, optional
        Type of the subassemblies (e.g. 'MP', 'TW', 'TP').
    subassembly_id : int or np.int64, optional
        ID of the subassembly.

    Returns
    -------
    dict
        Dictionary with the following keys:

        - "data": Pandas dataframe with the location data for each
          project
        - "exists": Boolean indicating whether matching records
          are found

    Examples
    --------
            >>> from unittest import mock
            >>> api = GeometryAPI(
            ...     api_root="https://example",
            ...     header={"Authorization": "Token test"},
            ... )
            >>> df = pd.DataFrame({"id": [1]})
            >>> with mock.patch.object(
            ...     GeometryAPI,
            ...     "process_data",
            ...     return_value=(df, {"existance": True}),
            ... ):
            ...     out = api.get_buildingblocks(projectsite="Site")
            >>> out["exists"]
            True
    """
    url_params = {}
    if projectsite is not None:
        url_params["sub_assembly__asset__projectsite__title"] = projectsite
    if assetlocation is not None:
        url_params["sub_assembly__asset__title"] = assetlocation
    if subassembly_type is not None:
        url_params["sub_assembly__subassembly_type"] = subassembly_type
    if subassembly_id is not None:
        url_params["sub_assembly__id"] = str(subassembly_id)
    url_data_type = "buildingblocks"
    output_type = "list"
    df, df_add = self.process_data(url_data_type, url_params, output_type)
    return {"data": df, "exists": df_add["existance"]}
get_materials
get_materials()

Get all the materials of building blocks.

Returns:

Type Description
dict

Dictionary with the following keys:

  • "data": Pandas dataframe with the location data for each project
  • "exists": Boolean indicating whether matching records are found

Examples:

    >>> from unittest import mock
    >>> api = GeometryAPI(
    ...     api_root="https://example",
    ...     header={"Authorization": "Token test"},
    ... )
    >>> df = pd.DataFrame({"id": [1]})
    >>> with mock.patch.object(
    ...     GeometryAPI,
    ...     "process_data",
    ...     return_value=(df, {"existance": True}),
    ... ):
    ...     out = api.get_materials()
    >>> out["exists"]
    True
Source code in src/owi/metadatabase/geometry/io.py
def get_materials(
    self,
) -> dict[str, Union[pd.DataFrame, bool, np.int64, None]]:
    """
    Get all the materials of building blocks.

    Returns
    -------
    dict
        Dictionary with the following keys:

        - "data": Pandas dataframe with the location data for each
          project
        - "exists": Boolean indicating whether matching records
          are found

    Examples
    --------
            >>> from unittest import mock
            >>> api = GeometryAPI(
            ...     api_root="https://example",
            ...     header={"Authorization": "Token test"},
            ... )
            >>> df = pd.DataFrame({"id": [1]})
            >>> with mock.patch.object(
            ...     GeometryAPI,
            ...     "process_data",
            ...     return_value=(df, {"existance": True}),
            ... ):
            ...     out = api.get_materials()
            >>> out["exists"]
            True
    """
    url_params = {}  # type: dict[str, str]
    url_data_type = "materials"
    output_type = "list"
    df, df_add = self.process_data(url_data_type, url_params, output_type)
    return {"data": df, "exists": df_add["existance"]}
get_subassembly_objects
get_subassembly_objects(
    turbine, subassembly=None, model_definition_id=None
)

Get all subassemblies for a given turbine, divided by type.

Parameters:

Name Type Description Default
turbine str

Turbine title.

required
subassembly str

Sub-assembly type (e.g. 'MP', 'TW', 'TP').

None
model_definition_id int or int64

ID of the model definition to filter the subassemblies.

None

Returns:

Type Description
dict

Dictionary with the following keys:

  • "TW": SubAssembly object for the tower
  • "TP": SubAssembly object for the transition piece
  • "MP": SubAssembly object for the monopile

Raises:

Type Description
ValueError

If no subassemblies found for the turbine or if no materials found in the database.

Examples:

>>> from types import SimpleNamespace
>>> from unittest import mock
>>> materials = pd.DataFrame(
...     [
...         {
...             "title": "Steel",
...             "slug": "steel",
...             "id": np.int64(1),
...             "description": "",
...             "young_modulus": np.float64(210000.0),
...             "density": np.float64(7850.0),
...             "poisson_ratio": np.float64(0.3),
...         }
...     ]
... )
>>> item = {
...     "id": np.int64(1),
...     "title": "SA_1",
...     "description": "",
...     "slug": "sa",
...     "x_position": np.float64(0),
...     "y_position": np.float64(0),
...     "z_position": np.float64(0),
...     "vertical_position_reference_system": "LAT",
...     "subassembly_type": "TW",
...     "source": "api",
...     "asset": np.int64(1),
...     "model_definition": np.int64(1),
... }
>>> response = SimpleNamespace(
...     status_code=200,
...     reason="OK",
...     json=lambda: [item],
... )
>>> api = GeometryAPI(
...     api_root="https://example",
...     header={"Authorization": "Token test"},
... )
>>> with mock.patch.object(
...     GeometryAPI,
...     "send_request",
...     return_value=response,
... ), mock.patch.object(
...     GeometryAPI,
...     "get_materials",
...     return_value={"exists": True, "data": materials},
... ):
...     out = api.get_subassembly_objects("T01")
>>> sorted(out.keys())
['TW']
Source code in src/owi/metadatabase/geometry/io.py
def get_subassembly_objects(
    self,
    turbine: str,
    subassembly: Union[str, None] = None,
    model_definition_id: Union[int, np.int64, None] = None,
) -> dict[str, SubAssembly]:
    """
    Get all subassemblies for a given turbine, divided by type.

    Parameters
    ----------
    turbine : str
        Turbine title.
    subassembly : str, optional
        Sub-assembly type (e.g. 'MP', 'TW', 'TP').
    model_definition_id : int or np.int64, optional
        ID of the model definition to filter the subassemblies.

    Returns
    -------
    dict
        Dictionary with the following keys:

        - "TW": SubAssembly object for the tower
        - "TP": SubAssembly object for the transition piece
        - "MP": SubAssembly object for the monopile

    Raises
    ------
    ValueError
        If no subassemblies found for the turbine or if no
        materials found in the database.

    Examples
    --------
    >>> from types import SimpleNamespace
    >>> from unittest import mock
    >>> materials = pd.DataFrame(
    ...     [
    ...         {
    ...             "title": "Steel",
    ...             "slug": "steel",
    ...             "id": np.int64(1),
    ...             "description": "",
    ...             "young_modulus": np.float64(210000.0),
    ...             "density": np.float64(7850.0),
    ...             "poisson_ratio": np.float64(0.3),
    ...         }
    ...     ]
    ... )
    >>> item = {
    ...     "id": np.int64(1),
    ...     "title": "SA_1",
    ...     "description": "",
    ...     "slug": "sa",
    ...     "x_position": np.float64(0),
    ...     "y_position": np.float64(0),
    ...     "z_position": np.float64(0),
    ...     "vertical_position_reference_system": "LAT",
    ...     "subassembly_type": "TW",
    ...     "source": "api",
    ...     "asset": np.int64(1),
    ...     "model_definition": np.int64(1),
    ... }
    >>> response = SimpleNamespace(
    ...     status_code=200,
    ...     reason="OK",
    ...     json=lambda: [item],
    ... )
    >>> api = GeometryAPI(
    ...     api_root="https://example",
    ...     header={"Authorization": "Token test"},
    ... )
    >>> with mock.patch.object(
    ...     GeometryAPI,
    ...     "send_request",
    ...     return_value=response,
    ... ), mock.patch.object(
    ...     GeometryAPI,
    ...     "get_materials",
    ...     return_value={"exists": True, "data": materials},
    ... ):
    ...     out = api.get_subassembly_objects("T01")
    >>> sorted(out.keys())
    ['TW']
    """
    url_data_type = "subassemblies"
    url_params = {"asset__title": turbine}
    if subassembly is not None:
        url_params["subassembly_type"] = subassembly
    if model_definition_id is not None:
        url_params["model_definition"] = str(model_definition_id)
    resp = self.send_request(url_data_type, url_params)
    self.check_request_health(resp)
    if not resp.json():
        raise ValueError("No subassemblies found for " + str(turbine))

    material_data = self.get_materials()
    if material_data["exists"]:
        materials = material_data["data"]
    else:
        raise ValueError("No materials found in the database.")

    subassemblies = {}
    for item in resp.json():
        subassembly_type = item["subassembly_type"]
        subassembly_obj = SubAssembly(materials, item, api_object=self)
        if subassembly_type in subassemblies:
            if not isinstance(subassemblies[subassembly_type], list):
                subassemblies[subassembly_type] = [subassemblies[subassembly_type]]
            subassemblies[subassembly_type].append(subassembly_obj)
        else:
            subassemblies[subassembly_type] = subassembly_obj

    return subassemblies
get_owt_geometry_processor
get_owt_geometry_processor(
    turbines,
    model_definition=None,
    tower_base=None,
    monopile_head=None,
)

Return the required processing class.

Will return data even if some turbines have issues given that at least one is fully OK.

Parameters:

Name Type Description Default
turbines str or list of str

Title(s) of the turbines.

required
model_definition str

Title of the model definition.

None
tower_base float or list of float

Height(s) of the tower base.

None
monopile_head float or list of float

Height(s) of the monopile head.

None

Returns:

Type Description
OWTs

Object containing information about all the requested turbines.

Raises:

Type Description
ValueError

If no materials found in the database or if all turbines encounter processing errors.

Examples:

>>> from unittest import mock
>>> api = GeometryAPI(
...     api_root="https://example",
...     header={"Authorization": "Token test"},
... )
>>> materials = pd.DataFrame({"id": [1]})
>>> location = pd.DataFrame({"projectsite_name": ["Site"]})
>>> subassemblies = pd.DataFrame({"subassembly_type": ["TW"]})
>>> def _make_owt(*args, **kwargs):
...     return "owt"
>>> def _make_owts(turbines, owts):
...     return {"turbines": turbines, "owts": owts}
>>> with mock.patch.object(
...     GeometryAPI,
...     "get_materials",
...     return_value={"exists": True, "data": materials},
... ), mock.patch.object(
...     api.loc_api,
...     "get_assetlocation_detail",
...     return_value={"exists": True, "data": location},
... ), mock.patch.object(
...     GeometryAPI,
...     "get_subassemblies",
...     return_value={"exists": True, "data": subassemblies},
... ), mock.patch.object(
...     GeometryAPI,
...     "_check_if_need_modeldef",
...     return_value=None,
... ), mock.patch(
...     "geometry.io.OWT",
...     _make_owt,
... ), mock.patch(
...     "geometry.io.OWTs",
...     _make_owts,
... ):
...     out = api.get_owt_geometry_processor("T01")
>>> out["turbines"]
['T01']
Source code in src/owi/metadatabase/geometry/io.py
def get_owt_geometry_processor(
    self,
    turbines: Union[str, list[str]],
    model_definition: Union[str, None] = None,
    tower_base: Union[float, list[float], None] = None,
    monopile_head: Union[float, list[float], None] = None,
) -> OWTs:
    """
    Return the required processing class.

    Will return data even if some turbines have issues given that at
    least one is fully OK.

    Parameters
    ----------
    turbines : str or list of str
        Title(s) of the turbines.
    model_definition : str, optional
        Title of the model definition.
    tower_base : float or list of float, optional
        Height(s) of the tower base.
    monopile_head : float or list of float, optional
        Height(s) of the monopile head.

    Returns
    -------
    OWTs
        Object containing information about all the requested
        turbines.

    Raises
    ------
    ValueError
        If no materials found in the database or if all turbines
        encounter processing errors.

    Examples
    --------
    >>> from unittest import mock
    >>> api = GeometryAPI(
    ...     api_root="https://example",
    ...     header={"Authorization": "Token test"},
    ... )
    >>> materials = pd.DataFrame({"id": [1]})
    >>> location = pd.DataFrame({"projectsite_name": ["Site"]})
    >>> subassemblies = pd.DataFrame({"subassembly_type": ["TW"]})
    >>> def _make_owt(*args, **kwargs):
    ...     return "owt"
    >>> def _make_owts(turbines, owts):
    ...     return {"turbines": turbines, "owts": owts}
    >>> with mock.patch.object(
    ...     GeometryAPI,
    ...     "get_materials",
    ...     return_value={"exists": True, "data": materials},
    ... ), mock.patch.object(
    ...     api.loc_api,
    ...     "get_assetlocation_detail",
    ...     return_value={"exists": True, "data": location},
    ... ), mock.patch.object(
    ...     GeometryAPI,
    ...     "get_subassemblies",
    ...     return_value={"exists": True, "data": subassemblies},
    ... ), mock.patch.object(
    ...     GeometryAPI,
    ...     "_check_if_need_modeldef",
    ...     return_value=None,
    ... ), mock.patch(
    ...     "geometry.io.OWT",
    ...     _make_owt,
    ... ), mock.patch(
    ...     "geometry.io.OWTs",
    ...     _make_owts,
    ... ):
    ...     out = api.get_owt_geometry_processor("T01")
    >>> out["turbines"]
    ['T01']
    """
    materials_data = self.get_materials()
    if materials_data["exists"]:
        materials = cast(pd.DataFrame, materials_data["data"])
    else:
        raise ValueError("No materials found in the database.")
    owts = []
    successful_turbines = []
    errors = []
    turbines = [turbines] if isinstance(turbines, str) else turbines
    if not isinstance(tower_base, list) and not isinstance(monopile_head, list):
        tower_base = [tower_base] * len(turbines)  # type: ignore
        monopile_head = [monopile_head] * len(turbines)  # type: ignore
    for i, turbine in enumerate(turbines):
        try:
            location_data = self.loc_api.get_assetlocation_detail(assetlocation=turbine)
            if location_data["exists"]:
                location = cast(pd.DataFrame, location_data["data"])
            else:
                raise ValueError(f"No location found for turbine {turbine}.")
            projectsite = location["projectsite_name"].loc[0]
            subassemblies_data = self.get_subassemblies(
                projectsite=projectsite,
                assetlocation=turbine,
                model_definition=model_definition,
            )
            if subassemblies_data["exists"]:
                subassemblies = subassemblies_data["data"]
                self._check_if_need_modeldef(subassemblies, turbine)
            else:
                raise ValueError(
                    f"No subassemblies found for turbine {turbine}. Please check model definition or database data."
                )
            owts.append(
                OWT(
                    self,
                    materials,
                    subassemblies,
                    location,
                    tower_base[i] if isinstance(tower_base, list) else tower_base,
                    (monopile_head[i] if isinstance(monopile_head, list) else monopile_head),
                )
            )
            successful_turbines.append(turbine)
        except ValueError as e:
            errors.append(str(e))
    if errors:
        if successful_turbines:
            warnings.warn(
                f"There were some errors during processing the request. "
                f"But some turbines were processed successfully: {', '.join(successful_turbines)}."
                f"\nErrors:\n" + "\n".join(errors),
                stacklevel=2,
            )
        else:
            raise ValueError("\n".join(errors))
    return OWTs(successful_turbines, owts)
get_monopile_pyles
get_monopile_pyles(
    projectsite,
    assetlocation,
    cutoff_point=nan,
    model_definition=None,
)

Return a dataframe with the monopile geometry.

Uses the mudline as reference.

Parameters:

Name Type Description Default
projectsite str

Name of the project site.

required
assetlocation str

Name of the wind turbine location.

required
cutoff_point float

Elevation of the load application point in (mLAT) above the mudline.

nan
model_definition str

Title of the model definition.

None

Returns:

Type Description
DataFrame

Dataframe with the monopile geometry.

Raises:

Type Description
ValueError

If no subassemblies or location found for turbine.

Examples:

>>> from unittest import mock
>>> api = GeometryAPI(
...     api_root="https://example",
...     header={"Authorization": "Token test"},
... )
>>> bbs = pd.DataFrame(
...     [
...         {
...             "z_position": 0,
...             "material_name": "Steel",
...             "density": 7850.0,
...             "wall_thickness": 20.0,
...             "bottom_outer_diameter": 6.0,
...             "top_outer_diameter": 6.0,
...             "youngs_modulus": 210000.0,
...             "poissons_ratio": 0.3,
...         },
...         {
...             "z_position": -1000,
...             "material_name": "Steel",
...             "density": 7850.0,
...             "wall_thickness": 20.0,
...             "bottom_outer_diameter": 6.0,
...             "top_outer_diameter": 6.0,
...             "youngs_modulus": 210000.0,
...             "poissons_ratio": 0.3,
...         },
...     ]
... )
>>> sas = pd.DataFrame({"z_position": [-50000]})
>>> location = pd.DataFrame({"elevation": [30.0]})
>>> with mock.patch.object(
...     GeometryAPI,
...     "get_buildingblocks",
...     return_value={"exists": True, "data": bbs},
... ), mock.patch.object(
...     GeometryAPI,
...     "get_subassemblies",
...     return_value={"exists": True, "data": sas},
... ), mock.patch.object(
...     GeometryAPI,
...     "_check_if_need_modeldef",
...     return_value=None,
... ), mock.patch.object(
...     api.loc_api,
...     "get_assetlocation_detail",
...     return_value={"exists": True, "data": location},
... ):
...     pile = api.get_monopile_pyles("Site", "T01")
>>> "Depth from [m]" in pile.columns
True
Source code in src/owi/metadatabase/geometry/io.py
def get_monopile_pyles(
    self,
    projectsite,
    assetlocation,
    cutoff_point=np.nan,
    model_definition: Union[str, None] = None,
):
    """
    Return a dataframe with the monopile geometry.

    Uses the mudline as reference.

    Parameters
    ----------
    projectsite : str
        Name of the project site.
    assetlocation : str
        Name of the wind turbine location.
    cutoff_point : float, optional
        Elevation of the load application point in (mLAT) above the
        mudline.
    model_definition : str, optional
        Title of the model definition.

    Returns
    -------
    pd.DataFrame
        Dataframe with the monopile geometry.

    Raises
    ------
    ValueError
        If no subassemblies or location found for turbine.

    Examples
    --------
    >>> from unittest import mock
    >>> api = GeometryAPI(
    ...     api_root="https://example",
    ...     header={"Authorization": "Token test"},
    ... )
    >>> bbs = pd.DataFrame(
    ...     [
    ...         {
    ...             "z_position": 0,
    ...             "material_name": "Steel",
    ...             "density": 7850.0,
    ...             "wall_thickness": 20.0,
    ...             "bottom_outer_diameter": 6.0,
    ...             "top_outer_diameter": 6.0,
    ...             "youngs_modulus": 210000.0,
    ...             "poissons_ratio": 0.3,
    ...         },
    ...         {
    ...             "z_position": -1000,
    ...             "material_name": "Steel",
    ...             "density": 7850.0,
    ...             "wall_thickness": 20.0,
    ...             "bottom_outer_diameter": 6.0,
    ...             "top_outer_diameter": 6.0,
    ...             "youngs_modulus": 210000.0,
    ...             "poissons_ratio": 0.3,
    ...         },
    ...     ]
    ... )
    >>> sas = pd.DataFrame({"z_position": [-50000]})
    >>> location = pd.DataFrame({"elevation": [30.0]})
    >>> with mock.patch.object(
    ...     GeometryAPI,
    ...     "get_buildingblocks",
    ...     return_value={"exists": True, "data": bbs},
    ... ), mock.patch.object(
    ...     GeometryAPI,
    ...     "get_subassemblies",
    ...     return_value={"exists": True, "data": sas},
    ... ), mock.patch.object(
    ...     GeometryAPI,
    ...     "_check_if_need_modeldef",
    ...     return_value=None,
    ... ), mock.patch.object(
    ...     api.loc_api,
    ...     "get_assetlocation_detail",
    ...     return_value={"exists": True, "data": location},
    ... ):
    ...     pile = api.get_monopile_pyles("Site", "T01")
    >>> "Depth from [m]" in pile.columns
    True
    """
    # Retrieve the monopile cans
    bbs = self.get_buildingblocks(
        projectsite=projectsite,
        assetlocation=assetlocation,
        subassembly_type="MP",
    )
    # Retrieve the monopile subassembly
    sas = self.get_subassemblies(
        projectsite=projectsite,
        assetlocation=assetlocation,
        subassembly_type="MP",
        model_definition=model_definition,
    )
    if sas["exists"]:
        subassemblies = cast(pd.DataFrame, sas["data"])
        self._check_if_need_modeldef(subassemblies, assetlocation)
    else:
        raise ValueError(
            f"No subassemblies found for turbine {assetlocation}. Please check model definition or database data."
        )
    # Water depth
    location_data = self.loc_api.get_assetlocation_detail(assetlocation=assetlocation, projectsite=projectsite)
    if location_data["exists"]:
        location = cast(pd.DataFrame, location_data["data"])
        water_depth = location["elevation"].values[0]
    else:
        raise ValueError(
            f"No location found for turbine {assetlocation} and hence no water depth can be retrieved."
        )

    # Calculate the pile penetration
    sas_df = cast(pd.DataFrame, sas["data"])
    toe_depth_lat = sas_df["z_position"].iloc[0]
    penetration = -((1e-3 * toe_depth_lat) - water_depth)

    # Create the pile for subsequent response analysis
    pile = pd.DataFrame()

    bbs_df = cast(pd.DataFrame, bbs["data"])
    for index in range(1, len(bbs_df)):
        prev_row = bbs_df.iloc[index - 1]
        row = bbs_df.iloc[index]
        pile.loc[index, "Depth to [m]"] = penetration - 1e-3 * float(prev_row.at["z_position"])
        pile.loc[index, "Depth from [m]"] = penetration - 1e-3 * float(row.at["z_position"])
        pile.loc[index, "Pile material"] = str(row.at["material_name"])
        pile.loc[index, "Pile material submerged unit weight [kN/m3]"] = 1e-2 * float(row.at["density"]) - 10
        pile.loc[index, "Wall thickness [mm]"] = float(row.at["wall_thickness"])
        pile.loc[index, "Diameter [m]"] = (
            1e-3 * 0.5 * (float(row.at["bottom_outer_diameter"]) + float(row.at["top_outer_diameter"]))
        )
        pile.loc[index, "Youngs modulus [GPa]"] = float(row.at["youngs_modulus"])
        pile.loc[index, "Poissons ratio [-]"] = float(row.at["poissons_ratio"])

    pile.sort_values("Depth from [m]", inplace=True)
    pile.reset_index(drop=True, inplace=True)

    # Cut off at the mudline
    if not np.isnan(cutoff_point):
        pile = pile.loc[pile["Depth to [m]"] > cutoff_point].reset_index(drop=True)
        pile.loc[0, "Depth from [m]"] = cutoff_point

    return pile
plot_turbines
plot_turbines(
    turbines,
    figures_per_line=5,
    return_fig=False,
    model_definition=None,
)

Plot turbines' frontal geometry.

Parameters:

Name Type Description Default
turbines str or list of str

Title(s) of the turbines.

required
figures_per_line int

Number of figures per line, default is 5.

5
return_fig bool

Boolean indicating whether to return the figure, default is False.

False
model_definition str

Title of the model definition.

None

Returns:

Type Description
Figure or None

Plotly figure object with selected turbines front profiles (if requested) or nothing.

Raises:

Type Description
ValueError

If no materials or subassemblies found in the database.

Examples:

>>> from unittest import mock
>>> class _StubSubassembly:
...     def __init__(self, *args, **kwargs):
...         self.building_blocks = []
...     def plotly(self):
...         layout = {
...             "scene": {},
...             "yaxis": {
...                 "title": {"text": "Height , mm"},
...                 "scaleanchor": "x",
...                 "scaleratio": 1,
...                 "type": "linear",
...             },
...         }
...         return [], layout
>>> api = GeometryAPI(
...     api_root="https://example",
...     header={"Authorization": "Token test"},
... )
>>> materials = pd.DataFrame({"id": [1]})
>>> subassemblies = pd.DataFrame({"subassembly_type": ["TW"]})
>>> with mock.patch.object(
...     GeometryAPI,
...     "get_materials",
...     return_value={"exists": True, "data": materials},
... ), mock.patch.object(
...     GeometryAPI,
...     "get_subassemblies",
...     return_value={"exists": True, "data": subassemblies},
... ), mock.patch.object(
...     GeometryAPI,
...     "_check_if_need_modeldef",
...     return_value=None,
... ), mock.patch(
...     "geometry.io.SubAssembly",
...     _StubSubassembly,
... ):
...     fig = api.plot_turbines(["T01"], return_fig=True)
>>> fig is not None
True
Source code in src/owi/metadatabase/geometry/io.py
def plot_turbines(
    self,
    turbines: Union[list[str], str],
    figures_per_line: int = 5,
    return_fig: bool = False,
    model_definition: Union[str, None] = None,
) -> Union[go.Figure, None]:
    """
    Plot turbines' frontal geometry.

    Parameters
    ----------
    turbines : str or list of str
        Title(s) of the turbines.
    figures_per_line : int, optional
        Number of figures per line, default is 5.
    return_fig : bool, optional
        Boolean indicating whether to return the figure, default
        is False.
    model_definition : str, optional
        Title of the model definition.

    Returns
    -------
    plotly.graph_objects.Figure or None
        Plotly figure object with selected turbines front profiles
        (if requested) or nothing.

    Raises
    ------
    ValueError
        If no materials or subassemblies found in the database.

    Examples
    --------
    >>> from unittest import mock
    >>> class _StubSubassembly:
    ...     def __init__(self, *args, **kwargs):
    ...         self.building_blocks = []
    ...     def plotly(self):
    ...         layout = {
    ...             "scene": {},
    ...             "yaxis": {
    ...                 "title": {"text": "Height , mm"},
    ...                 "scaleanchor": "x",
    ...                 "scaleratio": 1,
    ...                 "type": "linear",
    ...             },
    ...         }
    ...         return [], layout
    >>> api = GeometryAPI(
    ...     api_root="https://example",
    ...     header={"Authorization": "Token test"},
    ... )
    >>> materials = pd.DataFrame({"id": [1]})
    >>> subassemblies = pd.DataFrame({"subassembly_type": ["TW"]})
    >>> with mock.patch.object(
    ...     GeometryAPI,
    ...     "get_materials",
    ...     return_value={"exists": True, "data": materials},
    ... ), mock.patch.object(
    ...     GeometryAPI,
    ...     "get_subassemblies",
    ...     return_value={"exists": True, "data": subassemblies},
    ... ), mock.patch.object(
    ...     GeometryAPI,
    ...     "_check_if_need_modeldef",
    ...     return_value=None,
    ... ), mock.patch(
    ...     "geometry.io.SubAssembly",
    ...     _StubSubassembly,
    ... ):
    ...     fig = api.plot_turbines(["T01"], return_fig=True)
    >>> fig is not None
    True
    """
    materials_data = self.get_materials()
    if materials_data["exists"]:
        materials = materials_data["data"]
    else:
        raise ValueError("No materials found in the database.")
    turbines = [turbines] if isinstance(turbines, str) else turbines
    if len(turbines) > figures_per_line:
        n_rows = len(turbines) // figures_per_line + 1
        n_cols = figures_per_line
        rows = [i for i in range(1, n_rows + 1) for _ in range(n_cols)]
        cols = [i for _ in range(n_rows) for i in range(1, n_cols + 1)]
    else:
        n_rows = 1
        n_cols = len(turbines)
        rows = [1 for _ in range(n_cols)]
        cols = list(range(1, n_cols + 1))
    autosize = not len(turbines) < 3
    fig = make_subplots(n_rows, n_cols, subplot_titles=turbines)
    for i, turbine in enumerate(turbines):
        subassemblies_data = self.get_subassemblies(
            assetlocation=turbine,
            model_definition=model_definition,
        )
        if subassemblies_data["exists"]:
            subassemblies = cast(pd.DataFrame, subassemblies_data["data"])
            self._check_if_need_modeldef(subassemblies, turbine)
        else:
            raise ValueError(
                f"No subassemblies found for turbine {turbine}. Please check model definition or database data."
            )
        for _, sa in subassemblies.iterrows():
            subassembly = SubAssembly(materials, cast(DataSA, sa.to_dict()), api_object=self)
            subassembly.building_blocks  # noqa: B018
            plotly_data = subassembly.plotly()
            for k in range(len(plotly_data[0])):
                fig.add_trace(plotly_data[0][k], row=rows[i], col=cols[i])
        plotly_layout = plotly_data[1]
        if i > 0:
            plotly_layout["scene" + str(i + 1)] = plotly_layout["scene"]
            plotly_layout["yaxis" + str(i + 1)] = plotly_layout["yaxis"]
            plotly_layout["yaxis" + str(i + 1)]["scaleanchor"] = "x" + str(i + 1)
            plotly_layout.pop("scene")
            plotly_layout.pop("yaxis")
            plotly_layout["yaxis" + str(i + 1)].pop("title")
        fig.update_layout(plotly_layout, autosize=autosize)
    if return_fig:
        return fig
    else:
        fig.show()
__eq__
__eq__(other)

Compare two instances of the API class.

Parameters:

Name Type Description Default
other object

Another instance of the API class or a dictionary.

required

Returns:

Type Description
bool

True if the instances are equal, False otherwise.

Raises:

Type Description
AssertionError

If comparison is not possible due to incompatible types.

Examples:

>>> api_1 = API(api_root="https://example", token="test")
>>> api_2 = API(api_root="https://example", token="test")
>>> api_1 == api_2
True
Source code in src/owi/metadatabase/io.py
def __eq__(self, other: object) -> bool:
    """
    Compare two instances of the API class.

    Parameters
    ----------
    other : object
        Another instance of the API class or a dictionary.

    Returns
    -------
    bool
        True if the instances are equal, False otherwise.

    Raises
    ------
    AssertionError
        If comparison is not possible due to incompatible types.

    Examples
    --------
    >>> api_1 = API(api_root="https://example", token="test")
    >>> api_2 = API(api_root="https://example", token="test")
    >>> api_1 == api_2
    True
    """
    if not isinstance(other, (API, dict)):
        return NotImplemented
    if isinstance(other, type(self)):
        comp = deepcompare(self, other)
        assert comp[0], comp[1]
    elif isinstance(other, dict):
        comp = deepcompare(self.__dict__, other)
        assert comp[0], comp[1]
    else:
        raise AssertionError("Comparison is not possible due to incompatible types!")
    return comp[0]
send_request
send_request(url_data_type, url_params)

Handle sending appropriate request.

Handles sending appropriate request according to the type of authentication.

Parameters:

Name Type Description Default
url_data_type str

Type of the data we want to request (according to database model).

required
url_params Mapping

Parameters to send with the request to the database.

required

Returns:

Type Description
Response

An instance of the Response object.

Raises:

Type Description
InvalidParameterError

If neither header nor username and password are defined.

Examples:

>>> from types import SimpleNamespace
>>> from unittest import mock
>>> response = SimpleNamespace(status_code=200, reason="OK")
>>> with mock.patch("requests.get", return_value=response):
...     api = API(api_root="https://example", token="test")
...     resp = api.send_request("/projects", {})
>>> resp is response
True
Source code in src/owi/metadatabase/io.py
def send_request(
    self,
    url_data_type: str,
    url_params: Mapping[str, Union[str, float, int, Sequence[Union[str, float, int]], None]],
) -> requests.Response:
    """
    Handle sending appropriate request.

    Handles sending appropriate request according to the type of
    authentication.

    Parameters
    ----------
    url_data_type : str
        Type of the data we want to request (according to database
        model).
    url_params : Mapping
        Parameters to send with the request to the database.

    Returns
    -------
    requests.Response
        An instance of the Response object.

    Raises
    ------
    InvalidParameterError
        If neither header nor username and password are defined.

    Examples
    --------
    >>> from types import SimpleNamespace
    >>> from unittest import mock
    >>> response = SimpleNamespace(status_code=200, reason="OK")
    >>> with mock.patch("requests.get", return_value=response):
    ...     api = API(api_root="https://example", token="test")
    ...     resp = api.send_request("/projects", {})
    >>> resp is response
    True
    """
    if self.header is not None:
        response = requests.get(
            url=self.api_root + url_data_type,
            headers=self.header,
            params=url_params,
        )
    else:
        if self.uname is None or self.password is None:
            e = "Either self.header or self.uname and self.password must be defined."
            raise InvalidParameterError(e)
        else:
            response = requests.get(
                url=self.api_root + url_data_type,
                auth=self.auth,
                params=url_params,
            )
    return response
check_request_health staticmethod
check_request_health(resp)

Check status code of the response and provide details.

Checks status code of the response to request and provides details if unexpected.

Parameters:

Name Type Description Default
resp Response

Instance of the Response object.

required

Raises:

Type Description
APIConnectionError

If response status code is not 200.

Examples:

>>> from types import SimpleNamespace
>>> ok = SimpleNamespace(status_code=200, reason="OK")
>>> API.check_request_health(ok)
Source code in src/owi/metadatabase/io.py
@staticmethod
def check_request_health(resp: requests.Response) -> None:
    """
    Check status code of the response and provide details.

    Checks status code of the response to request and provides
    details if unexpected.

    Parameters
    ----------
    resp : requests.Response
        Instance of the Response object.

    Raises
    ------
    APIConnectionError
        If response status code is not 200.

    Examples
    --------
    >>> from types import SimpleNamespace
    >>> ok = SimpleNamespace(status_code=200, reason="OK")
    >>> API.check_request_health(ok)
    """
    if resp.status_code != 200:
        message = f"Error {resp.status_code}.\n{resp.reason}\n{resp.text}"
        raise APIConnectionError(message=message, response=resp)
output_to_df staticmethod
output_to_df(response)

Transform output to Pandas dataframe.

Parameters:

Name Type Description Default
response Response

Raw output of the sent request.

required

Returns:

Type Description
DataFrame

Pandas dataframe of the data from the output.

Raises:

Type Description
DataProcessingError

If failed to decode JSON from API response.

Examples:

>>> from types import SimpleNamespace
>>> resp = SimpleNamespace(text='[{"a": 1}]')
>>> int(API.output_to_df(resp)["a"].iloc[0])
1
Source code in src/owi/metadatabase/io.py
@staticmethod
def output_to_df(response: requests.Response) -> pd.DataFrame:
    """
    Transform output to Pandas dataframe.

    Parameters
    ----------
    response : requests.Response
        Raw output of the sent request.

    Returns
    -------
    pd.DataFrame
        Pandas dataframe of the data from the output.

    Raises
    ------
    DataProcessingError
        If failed to decode JSON from API response.

    Examples
    --------
    >>> from types import SimpleNamespace
    >>> resp = SimpleNamespace(text='[{"a": 1}]')
    >>> int(API.output_to_df(resp)["a"].iloc[0])
    1
    """
    try:
        data = json.loads(response.text)
    except Exception as err:
        raise DataProcessingError("Failed to decode JSON from API response") from err
    return pd.DataFrame(data)
postprocess_data staticmethod
postprocess_data(df, output_type)

Process dataframe information to extract additional data.

Processes dataframe information to extract the necessary additional data.

Parameters:

Name Type Description Default
df DataFrame

Dataframe of the output data.

required
output_type str

Expected type (amount) of the data extracted.

required

Returns:

Type Description
PostprocessData

Dictionary of the additional data extracted.

Raises:

Type Description
InvalidParameterError

If more than one record was returned for 'single' output type, or if output type is not 'single' or 'list'.

Examples:

>>> df = pd.DataFrame({"id": [1]})
>>> int(API.postprocess_data(df, "single")["id"])
1
Source code in src/owi/metadatabase/io.py
@staticmethod
def postprocess_data(df: pd.DataFrame, output_type: str) -> PostprocessData:
    """
    Process dataframe information to extract additional data.

    Processes dataframe information to extract the necessary
    additional data.

    Parameters
    ----------
    df : pd.DataFrame
        Dataframe of the output data.
    output_type : str
        Expected type (amount) of the data extracted.

    Returns
    -------
    PostprocessData
        Dictionary of the additional data extracted.

    Raises
    ------
    InvalidParameterError
        If more than one record was returned for 'single' output
        type, or if output type is not 'single' or 'list'.

    Examples
    --------
    >>> df = pd.DataFrame({"id": [1]})
    >>> int(API.postprocess_data(df, "single")["id"])
    1
    """
    if output_type == "single":
        if df.__len__() == 0:
            exists = False
            project_id = None
        elif df.__len__() == 1:
            exists = True
            project_id = df["id"].iloc[0]
        else:
            raise InvalidParameterError("More than one project site was returned, check search criteria.")
        data_add: PostprocessData = {
            "existance": exists,
            "id": project_id,
            "response": None,
        }
    elif output_type == "list":
        exists = df.__len__() != 0
        data_add: PostprocessData = {
            "existance": exists,
            "id": None,
            "response": None,
        }
    else:
        raise InvalidParameterError("Output type must be either 'single' or 'list', not " + output_type + ".")
    return data_add
validate_data staticmethod
validate_data(df, data_type)

Validate the data extracted from the database.

Parameters:

Name Type Description Default
df DataFrame

Dataframe of the output data.

required
data_type str

Type of the data we want to request (according to database model).

required

Returns:

Type Description
DataFrame

Dataframe with corrected data.

Examples:

>>> df = pd.DataFrame()
>>> API.validate_data(df, "subassemblies").empty
True
Source code in src/owi/metadatabase/io.py
@staticmethod
def validate_data(df: pd.DataFrame, data_type: str) -> pd.DataFrame:
    """
    Validate the data extracted from the database.

    Parameters
    ----------
    df : pd.DataFrame
        Dataframe of the output data.
    data_type : str
        Type of the data we want to request (according to database
        model).

    Returns
    -------
    pd.DataFrame
        Dataframe with corrected data.

    Examples
    --------
    >>> df = pd.DataFrame()
    >>> API.validate_data(df, "subassemblies").empty
    True
    """
    z_sa_mp = {"min": -100000, "max": -10000}
    z_sa_tp = {"min": -20000, "max": -1000}
    z_sa_tw = {"min": 1000, "max": 100000}
    sa_type = ["TW", "TP", "MP"]
    z = [z_sa_tw, z_sa_tp, z_sa_mp]
    if data_type == "subassemblies":
        if df.__len__() == 0:
            return df
        for i, sat in enumerate(sa_type):
            cond_small_units = (df["subassembly_type"] == sat) & (df["z_position"] < z[i]["min"])
            cond_big_units = (df["subassembly_type"] == sat) & (df["z_position"] > z[i]["max"])
            if df[cond_small_units].__len__() > 0:
                df.loc[cond_small_units, "z_position"] = df.loc[cond_small_units, "z_position"] / 1e3
                warnings.warn(
                    f"The value of z location for {df.loc[cond_small_units | cond_big_units, 'title'].values} \
                    might be wrong or in wrong units! There will be an attempt to correct the units.",
                    stacklevel=2,
                )
            if df[cond_big_units].__len__() > 0:
                df.loc[cond_big_units, "z_position"] = df.loc[cond_big_units, "z_position"] * 1e3
                warnings.warn(
                    f"The value of z location for {df.loc[cond_small_units | cond_big_units, 'title'].values} \
                    might be wrong or in wrong units! There will be an attempt to correct the units.",
                    stacklevel=2,
                )
    return df
process_data
process_data(url_data_type, url_params, output_type)

Process output data according to specified request parameters.

Parameters:

Name Type Description Default
url_data_type str

Type of the data we want to request (according to database model).

required
url_params Mapping

Parameters to send with the request to the database.

required
output_type str

Expected type (amount) of the data extracted.

required

Returns:

Type Description
tuple

A tuple of dataframe with the requested data and additional data from postprocessing.

Examples:

>>> from types import SimpleNamespace
>>> from unittest import mock
>>> response = SimpleNamespace(text="[]", status_code=200, reason="OK")
>>> api = API(api_root="https://example", token="test")
>>> with mock.patch.object(API, "send_request", return_value=response):
...     df, info = api.process_data("projects", {}, "list")
>>> df.empty
True
>>> info["existance"]
False
Source code in src/owi/metadatabase/io.py
def process_data(
    self,
    url_data_type: str,
    url_params: Mapping[str, Union[str, float, int, Sequence[Union[str, float, int]], None]],
    output_type: str,
) -> tuple[pd.DataFrame, PostprocessData]:
    """
    Process output data according to specified request parameters.

    Parameters
    ----------
    url_data_type : str
        Type of the data we want to request (according to database
        model).
    url_params : Mapping
        Parameters to send with the request to the database.
    output_type : str
        Expected type (amount) of the data extracted.

    Returns
    -------
    tuple
        A tuple of dataframe with the requested data and
        additional data from postprocessing.

    Examples
    --------
    >>> from types import SimpleNamespace
    >>> from unittest import mock
    >>> response = SimpleNamespace(text="[]", status_code=200, reason="OK")
    >>> api = API(api_root="https://example", token="test")
    >>> with mock.patch.object(API, "send_request", return_value=response):
    ...     df, info = api.process_data("projects", {}, "list")
    >>> df.empty
    True
    >>> info["existance"]
    False
    """
    resp = self.send_request(url_data_type, url_params)
    self.check_request_health(resp)
    df = self.output_to_df(resp)
    df = self.validate_data(df, url_data_type)
    df_add = self.postprocess_data(df, output_type)
    # Add the response object to the returned dictionary so tests can inspect it
    df_add["response"] = resp
    return df, df_add

geometry.processing — Data Processing

processing

Module containing the processing functions for the geometry data.

Classes

OWT
OWT(
    api,
    materials,
    subassemblies,
    location,
    tower_base=None,
    pile_head=None,
)

Class to process the geometry data of a single OWT.

:param api: API object used to call get_* methods. :param materials: Pandas dataframe with the materials data. :param sub_assemblies: Dictionary of the subassemblies. :param tw_sub_assemblies: Pandas dataframe with the tower subassemblies data for a given turbine. :param tp_sub_assemblies: Pandas dataframe with the transition piece subassemblies data for a given turbine. :param mp_sub_assemblies: Pandas dataframe with the monopile subassemblies data for a given turbine. :param tower_base: Elevation of the OWT tower base in mLAT. :param pile_head: Elevation of the pile head in mLAT. :param water_depth: Water depth in mLAT. :param pile_toe: Elevation of the pile toe in mLAT. :param rna: Pandas dataframe with the RNA data. :param tower: Pandas dataframe with the tower data. :param transition_piece: Pandas dataframe with the transition piece data. :param monopile: Pandas dataframe with the monopile data. :param tw_lumped_mass: Pandas dataframe with the lumped masses data for the tower. :param tp_lumped_mass: Pandas dataframe with the lumped masses data for the transition piece. :param mp_lumped_mass: Pandas dataframe with the lumped masses data for the monopile. :param tp_distributed_mass: Pandas dataframe with the distributed masses data for the transition piece. :param mp_distributed_mass: Pandas dataframe with the distributed masses data for the monopile. :param grout: Pandas dataframe with the grout data. :param full_structure: Pandas dataframe with the full structure data. :param tp_skirt: Pandas dataframe with the transition piece skirt data. :param substructure: Pandas dataframe with the substructure data.

Create an instance of the OWT class with required parameters.

Parameters:

Name Type Description Default
api Any

API object used to call get_* methods.

required
materials DataFrame or bool or int64 or None

Pandas dataframe with the materials data.

required
subassemblies DataFrame or bool or int64 or None

Pandas dataframe with the subassemblies data for a given turbine.

required
location DataFrame or bool or int64 or None

Pandas dataframe with the location data for a given turbine.

required
tower_base float64

Elevation of the OWT tower base in mLAT.

None
pile_head float64

Elevation of the pile head in mLAT.

None

Examples:

>>> from contextlib import ExitStack
>>> from unittest import mock
>>> location = pd.DataFrame({"elevation": [30.0]})
>>> def _set_subassemblies(self, subassemblies):
...     self.sub_assemblies = {}
>>> def _set_members(self):
...     return None
>>> with mock.patch.object(
...     OWT,
...     "_set_subassemblies",
...     _set_subassemblies,
... ), mock.patch.object(OWT, "_set_members", _set_members):
...     owt = OWT(
...         api=object(),
...         materials=pd.DataFrame(),
...         subassemblies=pd.DataFrame(),
...         location=location,
...     )
>>> float(owt.water_depth)
30.0
Source code in src/owi/metadatabase/geometry/processing.py
def __init__(
    self,
    api: Any,
    materials: Union[pd.DataFrame, bool, np.int64, None],
    subassemblies: Union[pd.DataFrame, bool, np.int64, None],
    location: Union[pd.DataFrame, bool, np.int64, None],
    tower_base: Union[np.float64, float, None] = None,
    pile_head: Union[np.float64, float, None] = None,
) -> None:
    """
    Create an instance of the OWT class with required parameters.

    Parameters
    ----------
    api : Any
        API object used to call get_* methods.
    materials : pd.DataFrame or bool or np.int64 or None
        Pandas dataframe with the materials data.
    subassemblies : pd.DataFrame or bool or np.int64 or None
        Pandas dataframe with the subassemblies data for a given
        turbine.
    location : pd.DataFrame or bool or np.int64 or None
        Pandas dataframe with the location data for a given
        turbine.
    tower_base : np.float64, optional
        Elevation of the OWT tower base in mLAT.
    pile_head : np.float64, optional
        Elevation of the pile head in mLAT.

    Examples
    --------
    >>> from contextlib import ExitStack
    >>> from unittest import mock
    >>> location = pd.DataFrame({"elevation": [30.0]})
    >>> def _set_subassemblies(self, subassemblies):
    ...     self.sub_assemblies = {}
    >>> def _set_members(self):
    ...     return None
    >>> with mock.patch.object(
    ...     OWT,
    ...     "_set_subassemblies",
    ...     _set_subassemblies,
    ... ), mock.patch.object(OWT, "_set_members", _set_members):
    ...     owt = OWT(
    ...         api=object(),
    ...         materials=pd.DataFrame(),
    ...         subassemblies=pd.DataFrame(),
    ...         location=location,
    ...     )
    >>> float(owt.water_depth)
    30.0
    """
    self._init_proc = False
    self._init_spec_part = False
    self._init_spec_full = False
    self.api = api
    materials_df = cast(pd.DataFrame, materials)
    subassemblies_df = cast(pd.DataFrame, subassemblies)
    location_df = cast(pd.DataFrame, location)
    self.materials = materials_df
    self._set_subassemblies(subassemblies_df)
    self.tw_sub_assemblies = None
    self.tp_sub_assemblies = None
    self.mp_sub_assemblies = None
    self._set_members()
    for attr in ATTR_PROC:
        setattr(self, attr, None)
    for attr in ATTR_SPEC:
        setattr(self, attr, None)
    self.water_depth = np.float64(location_df["elevation"].values[0])
    if not tower_base or not pile_head:
        if "TW" in self.sub_assemblies:
            self.tower_base = self.sub_assemblies["TW"].absolute_bottom
        elif "TP" in self.sub_assemblies:
            self.tower_base = self.sub_assemblies["TP"].absolute_top
        else:
            self.tower_base = None
        if "MP" in self.sub_assemblies:
            self.pile_head = self.sub_assemblies["MP"].absolute_top
        else:
            self.pile_head = None
    else:
        self.tower_base = tower_base
        self.pile_head = pile_head
Functions
set_df_structure
set_df_structure(idx)

Calculate and/or convert geometrical data of subassemblies.

Calculates and/or converts geometrical data of subassemblies from the database.

Parameters:

Name Type Description Default
idx str

Possible index to identify corresponding subassembly.

required

Returns:

Type Description
DataFrame

Dataframe containing geometry data from database with z in mLAT system.

Raises:

Type Description
ValueError

If subassembly data not found or unknown index.

Source code in src/owi/metadatabase/geometry/processing.py
def set_df_structure(self, idx: str) -> pd.DataFrame:
    """
    Calculate and/or convert geometrical data of subassemblies.

    Calculates and/or converts geometrical data of subassemblies
    from the database.

    Parameters
    ----------
    idx : str
        Possible index to identify corresponding subassembly.

    Returns
    -------
    pd.DataFrame
        Dataframe containing geometry data from database with z in
        mLAT system.

    Raises
    ------
    ValueError
        If subassembly data not found or unknown index.
    """
    cols = [
        "OD",
        "height",
        "mass",
        "volume",
        "wall_thickness",
        "x",
        "y",
        "z",
    ]
    if idx == "tw":
        if self.tw_sub_assemblies is None:
            raise ValueError("Tower subassembly data not found.")
        df_index = self.tw_sub_assemblies.index.str.contains(idx)
        df = deepcopy(self.tw_sub_assemblies.loc[df_index, cols])
        depth_to = self.tower_base + df.z * 1e-3
        depth_from = depth_to + df.height * 1e-3
    elif idx == "tp":
        if self.tp_sub_assemblies is None:
            raise ValueError("Transition piece subassembly data not found.")
        # We don't take into account the grout, this element will be modelled as a distributed lumped mass.
        df_index = (self.tp_sub_assemblies.index.str.contains(idx)) & (
            ~self.tp_sub_assemblies.index.str.contains("grout")
        )
        df = deepcopy(self.tp_sub_assemblies.loc[df_index, cols])
        bottom_tp = self.tower_base - df["height"].sum() * 1e-3
        depth_to = bottom_tp + df.z * 1e-3
        depth_from = depth_to + df.height * 1e-3
    elif idx == "mp":
        if self.mp_sub_assemblies is None:
            raise ValueError("Monopile subassembly data not found.")
        df_index = self.mp_sub_assemblies.index.str.contains(idx)
        df = deepcopy(self.mp_sub_assemblies.loc[df_index, cols])
        toe = self.pile_head - df["height"].sum() * 1e-3
        self.pile_toe = round(toe, 3)
        depth_to = toe + df.z * 1e-3
        depth_from = depth_to + df.height * 1e-3
    else:
        raise ValueError("Unknown index.")
    df["Elevation from [mLAT]"] = depth_from
    df["Elevation to [mLAT]"] = depth_to
    # Round elevations to mm to avoid numerical inconsistencies later when setting altitude values to apply loads.
    df = df.round({"Elevation from [mLAT]": 3, "Elevation to [mLAT]": 3})
    return df
process_structure_geometry
process_structure_geometry(idx)

Calculate and/or convert geometrical data for FE models.

Calculates and/or converts geometrical data of subassemblies from the database to use as input for FE models.

Parameters:

Name Type Description Default
idx str

Possible index to identify corresponding subassembly.

required

Returns:

Type Description
DataFrame

Dataframe consisting of the required data to build FE models.

Source code in src/owi/metadatabase/geometry/processing.py
def process_structure_geometry(self, idx: str) -> pd.DataFrame:
    """
    Calculate and/or convert geometrical data for FE models.

    Calculates and/or converts geometrical data of subassemblies
    from the database to use as input for FE models.

    Parameters
    ----------
    idx : str
        Possible index to identify corresponding subassembly.

    Returns
    -------
    pd.DataFrame
        Dataframe consisting of the required data to build FE
        models.
    """
    df = self.set_df_structure(idx)
    df["height"] = pd.to_numeric(df["height"])
    df["wall_thickness"] = pd.to_numeric(df["wall_thickness"])
    df.rename(columns={"wall_thickness": "Wall thickness [mm]"}, inplace=True)
    df.rename(columns={"volume": "Volume [m3]"}, inplace=True)
    d_to = [d.split("/", 1)[0] for d in df["OD"].values]
    d_from = [d.split("/", 1)[1] if len(d.split("/", 1)) > 1 else d.split("/", 1)[0] for d in df["OD"].values]
    df["Diameter from [m]"] = np.array(d_from, dtype=float) * 1e-3
    df["Diameter to [m]"] = np.array(d_to, dtype=float) * 1e-3
    df["rho [t/m]"] = df["mass"] / df["height"]
    df["Mass [t]"] = df["mass"] * 1e-3
    df["Height [m]"] = df["height"] * 1e-3
    df["Youngs modulus [GPa]"] = 210
    df["Poissons ratio [-]"] = 0.3
    cols = [
        "Elevation from [mLAT]",
        "Elevation to [mLAT]",
        "Height [m]",
        "Diameter from [m]",
        "Diameter to [m]",
        "Volume [m3]",
        "Wall thickness [mm]",
        "Youngs modulus [GPa]",
        "Poissons ratio [-]",
        "Mass [t]",
        "rho [t/m]",
    ]
    return df.loc[:, cols].copy()
process_rna
process_rna()

Set dataframe with required properties to model the RNA system.

Raises:

Type Description
ValueError

If tower subassembly data not found.

Source code in src/owi/metadatabase/geometry/processing.py
def process_rna(self) -> None:
    """
    Set dataframe with required properties to model the RNA system.

    Raises
    ------
    ValueError
        If tower subassembly data not found.
    """
    if self.tw_sub_assemblies is None:
        raise ValueError("Tower subassembly data not found.")
    rna_index = self.tw_sub_assemblies.index.str.contains("RNA")
    rna = deepcopy(
        self.tw_sub_assemblies.loc[
            rna_index,
            ["mass", "moment_of_inertia", "x", "y", "z", "description"],
        ]
    )
    mi = rna["moment_of_inertia"].values
    i_xx, i_yy, i_zz = [], [], []
    for m in mi:
        i_xx.append(m["x"] * 1e-3)
        i_yy.append(m["y"] * 1e-3)
        i_zz.append(m["z"] * 1e-3)
    rna["Ixx [tm2]"] = i_xx
    rna["Iyy [tm2]"] = i_yy
    rna["Izz [tm2]"] = i_zz
    rna["Mass [t]"] = rna["mass"] * 1e-3
    rna["X [m]"] = rna["x"] * 1e-3
    rna["Y [m]"] = rna["y"] * 1e-3
    rna["Z [mLAT]"] = self.tower_base + rna["z"] * 1e-3
    rna.rename(columns={"description": "Description"}, inplace=True)
    cols = [
        "X [m]",
        "Y [m]",
        "Z [mLAT]",
        "Mass [t]",
        "Ixx [tm2]",
        "Iyy [tm2]",
        "Izz [tm2]",
        "Description",
    ]
    self.rna = rna[cols]
set_df_appurtenances
set_df_appurtenances(idx)

Set dataframe with required properties for concentrated masses.

Sets dataframe containing the required properties to model concentrated masses from database subassemblies.

Parameters:

Name Type Description Default
idx str

Index to identify corresponding subassembly with possible values: 'TW', 'TP', 'MP'.

required

Returns:

Type Description
DataFrame

Dataframe containing lumped masses data from database with Z coordinates in mLAT system.

Raises:

Type Description
ValueError

If subassembly data not found or unknown index.

Source code in src/owi/metadatabase/geometry/processing.py
def set_df_appurtenances(self, idx: str) -> pd.DataFrame:
    """
    Set dataframe with required properties for concentrated masses.

    Sets dataframe containing the required properties to model
    concentrated masses from database subassemblies.

    Parameters
    ----------
    idx : str
        Index to identify corresponding subassembly with possible
        values: 'TW', 'TP', 'MP'.

    Returns
    -------
    pd.DataFrame
        Dataframe containing lumped masses data from database with
        Z coordinates in mLAT system.

    Raises
    ------
    ValueError
        If subassembly data not found or unknown index.
    """
    cols = ["mass", "x", "y", "z", "description"]
    if idx == "TW":
        if self.tw_sub_assemblies is None:
            raise ValueError("Tower subassembly data not found.")
        df_index = self.tw_sub_assemblies.index.str.contains(idx)
        df = deepcopy(self.tw_sub_assemblies.loc[df_index, cols])
        df["Z [mLAT]"] = self.tower_base + df["z"] * 1e-3
    elif idx == "TP":
        if self.tp_sub_assemblies is None:
            raise ValueError("Transition piece subassembly data not found.")
        df_index = self.tp_sub_assemblies.index.str.contains(idx)
        df = deepcopy(self.tp_sub_assemblies.loc[df_index, cols + ["height"]])
        # Lumped masses have 'None' height whereas distributed masses present not 'None' values
        df["height"] = pd.to_numeric(df["height"])
        df = df[df["height"].isnull()]
        bottom = self.sub_assemblies["TP"].position.z * 1e-3  # m
        df["Z [mLAT]"] = bottom + df["z"] * 1e-3  # m
    elif idx == "MP":
        if self.mp_sub_assemblies is None:
            raise ValueError("Monopile subassembly data not found.")
        df_index = self.mp_sub_assemblies.index.str.contains(idx)
        df = deepcopy(self.mp_sub_assemblies.loc[df_index, cols + ["height"]])
        # Lumped masses have 'None' height whereas distributed masses present not 'None' values
        df["height"] = pd.to_numeric(df["height"])
        df = df[df["height"].isnull()]
        bottom = self.pile_toe
        df["Z [mLAT]"] = bottom + df["z"] * 1e-3
    else:
        raise ValueError("Unknown index.")
    return df
process_lumped_masses
process_lumped_masses(idx)

Create dataframe with required properties for lumped masses.

Creates dataframe containing the required properties to model lumped mass appurtenances. Note that if the preprocessor package does not find any appurtenances it'll return an empty dataframe.

Parameters:

Name Type Description Default
idx str

Index to identify corresponding subassembly with possible values: 'TW', 'TP', 'MP'.

required

Returns:

Type Description
DataFrame

Dataframe with lumped mass properties.

Source code in src/owi/metadatabase/geometry/processing.py
def process_lumped_masses(self, idx: str) -> pd.DataFrame:
    """
    Create dataframe with required properties for lumped masses.

    Creates dataframe containing the required properties to model
    lumped mass appurtenances. Note that if the preprocessor
    package does not find any appurtenances it'll return an empty
    dataframe.

    Parameters
    ----------
    idx : str
        Index to identify corresponding subassembly with possible
        values: 'TW', 'TP', 'MP'.

    Returns
    -------
    pd.DataFrame
        Dataframe with lumped mass properties.
    """
    df = self.set_df_appurtenances(idx)
    df["Mass [t]"] = df.mass * 1e-3
    df["X [m]"] = df.x * 1e-3
    df["Y [m]"] = df.y * 1e-3
    df.rename(columns={"description": "Description"}, inplace=True)
    cols = ["X [m]", "Y [m]", "Z [mLAT]", "Mass [t]", "Description"]
    return df.loc[:, cols].copy()
set_df_distributed_appurtenances
set_df_distributed_appurtenances(idx)

Set dataframe with required properties for distributed masses.

Sets dataframe containing the required properties to model distributed lumped masses from database.

Parameters:

Name Type Description Default
idx str

Index to identify corresponding subassembly with possible values: 'TW', 'TP', 'MP'.

required

Returns:

Type Description
DataFrame

Dataframe containing distributed lumped masses data from database. Z coordinates in mLAT system.

Raises:

Type Description
ValueError

If subassembly data not found or unknown index or distributed lumped masses located outside the transition piece.

Source code in src/owi/metadatabase/geometry/processing.py
def set_df_distributed_appurtenances(self, idx: str) -> pd.DataFrame:
    """
    Set dataframe with required properties for distributed masses.

    Sets dataframe containing the required properties to model
    distributed lumped masses from database.

    Parameters
    ----------
    idx : str
        Index to identify corresponding subassembly with possible
        values: 'TW', 'TP', 'MP'.

    Returns
    -------
    pd.DataFrame
        Dataframe containing distributed lumped masses data from
        database. Z coordinates in mLAT system.

    Raises
    ------
    ValueError
        If subassembly data not found or unknown index or
        distributed lumped masses located outside the transition
        piece.
    """
    cols = ["mass", "x", "y", "z", "height", "volume", "description"]
    if idx == "TP":
        if self.tp_sub_assemblies is None:
            raise ValueError("Transition piece subassembly data not found.")
        df_index = self.tp_sub_assemblies.index.str.contains(idx)
        df = deepcopy(self.tp_sub_assemblies.loc[df_index, cols])
        # Lumped masses have 'None' height whereas distributed masses present not 'None' values
        df["height"] = pd.to_numeric(df["height"])
        df = df[df["height"].notnull()]
        bottom_tp = self.tower_base - self.tp_sub_assemblies.iloc[0]["z"] * 1e-3
        df["Z [mLAT]"] = bottom_tp + df["z"] * 1e-3
    elif idx == "MP":
        if self.mp_sub_assemblies is None:
            raise ValueError("Monopile subassembly data not found.")
        df_index = self.mp_sub_assemblies.index.str.contains(idx)
        df = deepcopy(self.mp_sub_assemblies.loc[df_index, cols])
        # Lumped masses have 'None' height whereas distributed masses present not 'None' values
        df["height"] = pd.to_numeric(df["height"])
        df = df[df["height"].notnull()]
        bottom = self.pile_toe
        df["Z [mLAT]"] = bottom + df["z"] * 1e-3
    elif idx == "grout":
        if self.tp_sub_assemblies is None:
            raise ValueError("Transition piece subassembly data not found.")
        df_index = self.tp_sub_assemblies.index.str.contains(idx)
        df = deepcopy(self.tp_sub_assemblies.loc[df_index, cols])
        # Lumped masses have 'None' height whereas distributed masses present not 'None' values
        df["height"] = pd.to_numeric(df["height"])
        df = df[df["height"].notnull()]
        bottom_tp = self.tower_base - self.tp_sub_assemblies.iloc[0]["z"] * 1e-3
        df["Z [mLAT]"] = bottom_tp + df["z"] * 1e-3
    else:
        raise ValueError("Unknown index or non distributed lumped masses located outside the transition piece.")
    return df
process_distributed_lumped_masses
process_distributed_lumped_masses(idx)

Create dataframe with uniformly distributed appurtenances.

Creates dataframe containing the required properties to model uniformly distributed appurtenances. Note that if the preprocessor package does not find any appurtenances it'll return an empty dataframe.

Parameters:

Name Type Description Default
idx str

Index to identify corresponding subassembly with possible values: 'TP', 'MP'.

required

Returns:

Type Description
DataFrame

Dataframe with distributed lumped mass properties.

Source code in src/owi/metadatabase/geometry/processing.py
def process_distributed_lumped_masses(self, idx: str) -> pd.DataFrame:
    """
    Create dataframe with uniformly distributed appurtenances.

    Creates dataframe containing the required properties to model
    uniformly distributed appurtenances. Note that if the
    preprocessor package does not find any appurtenances it'll
    return an empty dataframe.

    Parameters
    ----------
    idx : str
        Index to identify corresponding subassembly with possible
        values: 'TP', 'MP'.

    Returns
    -------
    pd.DataFrame
        Dataframe with distributed lumped mass properties.
    """
    df = self.set_df_distributed_appurtenances(idx)
    df["Mass [t]"] = df["mass"] * 1e-3
    df["X [m]"] = df["x"] * 1e-3
    df["Y [m]"] = df["y"] * 1e-3
    df["Height [m]"] = df["height"] * 1e-3
    df.rename(columns={"volume": "Volume [m3]"}, inplace=True)
    df.rename(columns={"description": "Description"}, inplace=True)
    cols = [
        "X [m]",
        "Y [m]",
        "Z [mLAT]",
        "Height [m]",
        "Mass [t]",
        "Volume [m3]",
        "Description",
    ]
    return df.loc[:, cols].copy()
process_structure
process_structure(option='full')

Set dataframe with required properties to model the tower.

Sets dataframe containing the required properties to model the tower geometry, including the RNA system.

Parameters:

Name Type Description Default
option str

Option to process the data for a specific subassembly. Possible values:

  • "full": To process all the data for all subassemblies.
  • "tower": To process only the data for the tower subassembly.
  • "TP": To process only the data for the transition piece subassembly.
  • "monopile": To process only the data for the monopile foundation subassembly.
'full'

Examples:

>>> from contextlib import ExitStack
>>> from unittest import mock
>>> location = pd.DataFrame({"elevation": [30.0]})
>>> def _set_subassemblies(self, subassemblies):
...     self.sub_assemblies = {}
>>> def _set_members(self):
...     return None
>>> with mock.patch.object(
...     OWT,
...     "_set_subassemblies",
...     _set_subassemblies,
... ), mock.patch.object(OWT, "_set_members", _set_members):
...     owt = OWT(
...         api=object(),
...         materials=pd.DataFrame(),
...         subassemblies=pd.DataFrame(),
...         location=location,
...     )
>>> empty_df = pd.DataFrame()
>>> with ExitStack() as stack:
...     _ = stack.enter_context(mock.patch.object(OWT, "process_rna"))
...     _ = stack.enter_context(
...         mock.patch.object(
...             OWT,
...             "process_structure_geometry",
...             return_value=empty_df,
...         )
...     )
...     _ = stack.enter_context(
...         mock.patch.object(
...             OWT,
...             "process_lumped_masses",
...             return_value=empty_df,
...         )
...     )
...     _ = stack.enter_context(
...         mock.patch.object(
...             OWT,
...             "process_distributed_lumped_masses",
...             return_value=empty_df,
...         )
...     )
...     owt.process_structure(option="TW")
>>> owt._init_proc
True
Source code in src/owi/metadatabase/geometry/processing.py
def process_structure(self, option="full") -> None:
    """
    Set dataframe with required properties to model the tower.

    Sets dataframe containing the required properties to model the
    tower geometry, including the RNA system.

    Parameters
    ----------
    option : str, optional
        Option to process the data for a specific subassembly.
        Possible values:

        - "full": To process all the data for all subassemblies.
        - "tower": To process only the data for the tower
          subassembly.
        - "TP": To process only the data for the transition piece
          subassembly.
        - "monopile": To process only the data for the monopile
          foundation subassembly.

    Examples
    --------
    >>> from contextlib import ExitStack
    >>> from unittest import mock
    >>> location = pd.DataFrame({"elevation": [30.0]})
    >>> def _set_subassemblies(self, subassemblies):
    ...     self.sub_assemblies = {}
    >>> def _set_members(self):
    ...     return None
    >>> with mock.patch.object(
    ...     OWT,
    ...     "_set_subassemblies",
    ...     _set_subassemblies,
    ... ), mock.patch.object(OWT, "_set_members", _set_members):
    ...     owt = OWT(
    ...         api=object(),
    ...         materials=pd.DataFrame(),
    ...         subassemblies=pd.DataFrame(),
    ...         location=location,
    ...     )
    >>> empty_df = pd.DataFrame()
    >>> with ExitStack() as stack:
    ...     _ = stack.enter_context(mock.patch.object(OWT, "process_rna"))
    ...     _ = stack.enter_context(
    ...         mock.patch.object(
    ...             OWT,
    ...             "process_structure_geometry",
    ...             return_value=empty_df,
    ...         )
    ...     )
    ...     _ = stack.enter_context(
    ...         mock.patch.object(
    ...             OWT,
    ...             "process_lumped_masses",
    ...             return_value=empty_df,
    ...         )
    ...     )
    ...     _ = stack.enter_context(
    ...         mock.patch.object(
    ...             OWT,
    ...             "process_distributed_lumped_masses",
    ...             return_value=empty_df,
    ...         )
    ...     )
    ...     owt.process_structure(option="TW")
    >>> owt._init_proc
    True
    """
    self._init_proc = True
    if option == "full":
        self.process_rna()
        self.tower = self.process_structure_geometry("tw")
        self.transition_piece = self.process_structure_geometry("tp")
        self.monopile = self.process_structure_geometry("mp")
        self.tw_lumped_mass = self.process_lumped_masses("TW")
        self.tp_lumped_mass = self.process_lumped_masses("TP")
        self.mp_lumped_mass = self.process_lumped_masses("MP")
        self.tp_distributed_mass = self.process_distributed_lumped_masses("TP")
        self.mp_distributed_mass = self.process_distributed_lumped_masses("MP")
        self.grout = self.process_distributed_lumped_masses("grout")
    elif option == "TW":
        self.process_rna()
        self.tower = self.process_structure_geometry("tw")
        self.tw_lumped_mass = self.process_lumped_masses("TW")
    elif option == "TP":
        self.transition_piece = self.process_structure_geometry("tp")
        self.tp_lumped_mass = self.process_lumped_masses("TP")
        self.tp_distributed_mass = self.process_distributed_lumped_masses("TP")
        self.grout = self.process_distributed_lumped_masses("grout")
    elif option == "MP":
        self.monopile = self.process_structure_geometry("mp")
        self.mp_lumped_mass = self.process_lumped_masses("MP")
        self.mp_distributed_mass = self.process_distributed_lumped_masses("MP")
can_adjust_properties staticmethod
can_adjust_properties(row)

Recalculate can properties based on section and elevations.

Recalculation of can properties based on section properties and can elevations: height [m], volume [m3], mass [t], rho [t/m].

Parameters:

Name Type Description Default
row Series

Original can properties.

required

Returns:

Type Description
Series

Pandas series of recalculated can properties.

Examples:

>>> row = pd.Series(
...     {
...         "Mass [t]": 10.0,
...         "Volume [m3]": 5.0,
...         "Elevation from [mLAT]": 10.0,
...         "Elevation to [mLAT]": 0.0,
...         "Diameter from [m]": 6.0,
...         "Diameter to [m]": 6.0,
...         "Wall thickness [mm]": 10.0,
...     }
... )
>>> out = OWT.can_adjust_properties(row)
>>> float(out["Height [m]"])
10.0
Source code in src/owi/metadatabase/geometry/processing.py
@staticmethod
def can_adjust_properties(row: pd.Series) -> pd.Series:
    """
    Recalculate can properties based on section and elevations.

    Recalculation of can properties based on section properties and
    can elevations: height [m], volume [m3], mass [t], rho [t/m].

    Parameters
    ----------
    row : pd.Series
        Original can properties.

    Returns
    -------
    pd.Series
        Pandas series of recalculated can properties.

    Examples
    --------
    >>> row = pd.Series(
    ...     {
    ...         "Mass [t]": 10.0,
    ...         "Volume [m3]": 5.0,
    ...         "Elevation from [mLAT]": 10.0,
    ...         "Elevation to [mLAT]": 0.0,
    ...         "Diameter from [m]": 6.0,
    ...         "Diameter to [m]": 6.0,
    ...         "Wall thickness [mm]": 10.0,
    ...     }
    ... )
    >>> out = OWT.can_adjust_properties(row)
    >>> float(out["Height [m]"])
    10.0
    """
    density = row["Mass [t]"] / row["Volume [m3]"]
    height = row["Elevation from [mLAT]"] - row["Elevation to [mLAT]"]
    r1 = row["Diameter from [m]"] / 2
    r2 = row["Diameter to [m]"] / 2
    volume_out = 1 / 3 * np.pi * (r1**2 + r1 * r2 + r2**2) * height
    wall_thickness = row["Wall thickness [mm]"] * 1e-3
    r1 = r1 - wall_thickness
    r2 = r2 - wall_thickness
    volume_in = 1 / 3 * np.pi * (r1**2 + r1 * r2 + r2**2) * height
    volume = volume_out - volume_in
    mass = volume * density
    rho_m = mass / height
    can_properties = pd.Series(
        data=[height, volume, mass, rho_m],
        index=["Height [m]", "Volume [m3]", "Mass [t]", "rho [t/m]"],
    )
    return can_properties
can_modification
can_modification(df, altitude, position='bottom')

Change can properties based on the altitude.

Parameters:

Name Type Description Default
df DataFrame

Dataframe containing the can properties.

required
altitude float64 or None

Altitude in mLAT.

required
position str

Position of the can with respect to the altitude with possible values: "bottom" or "top", default is "bottom".

'bottom'

Returns:

Type Description
DataFrame

Dataframe with the modified can properties.

Examples:

>>> df = pd.DataFrame(
...     {
...         "Elevation from [mLAT]": [10.0],
...         "Elevation to [mLAT]": [0.0],
...         "Diameter from [m]": [6.0],
...         "Diameter to [m]": [6.0],
...         "Wall thickness [mm]": [10.0],
...         "Volume [m3]": [5.0],
...         "Mass [t]": [10.0],
...         "rho [t/m]": [1.0],
...     },
...     index=["A"],
... )
>>> from types import SimpleNamespace
>>> helper = SimpleNamespace(can_adjust_properties=OWT.can_adjust_properties)
>>> out = OWT.can_modification(helper, df.copy(), np.float64(5.0))
>>> float(out["Elevation to [mLAT]"].iloc[0])
5.0
Source code in src/owi/metadatabase/geometry/processing.py
def can_modification(
    self,
    df: pd.DataFrame,
    altitude: Union[np.float64, float, None],
    position: str = "bottom",
) -> pd.DataFrame:
    """
    Change can properties based on the altitude.

    Parameters
    ----------
    df : pd.DataFrame
        Dataframe containing the can properties.
    altitude : np.float64 or None
        Altitude in mLAT.
    position : str, optional
        Position of the can with respect to the altitude with
        possible values: "bottom" or "top", default is "bottom".

    Returns
    -------
    pd.DataFrame
        Dataframe with the modified can properties.

    Examples
    --------
    >>> df = pd.DataFrame(
    ...     {
    ...         "Elevation from [mLAT]": [10.0],
    ...         "Elevation to [mLAT]": [0.0],
    ...         "Diameter from [m]": [6.0],
    ...         "Diameter to [m]": [6.0],
    ...         "Wall thickness [mm]": [10.0],
    ...         "Volume [m3]": [5.0],
    ...         "Mass [t]": [10.0],
    ...         "rho [t/m]": [1.0],
    ...     },
    ...     index=["A"],
    ... )
    >>> from types import SimpleNamespace
    >>> helper = SimpleNamespace(can_adjust_properties=OWT.can_adjust_properties)
    >>> out = OWT.can_modification(helper, df.copy(), np.float64(5.0))
    >>> float(out["Elevation to [mLAT]"].iloc[0])
    5.0
    """
    if position == "bottom":
        ind = -1
        _col = " to "
    else:
        ind = 0
        _col = " from "
    altitude_val = float(altitude) if altitude is not None else float("nan")
    row_index = df.index[ind]
    df.loc[row_index, "Elevation" + _col + "[mLAT]"] = altitude_val
    col_elev_from = df.columns.get_loc("Elevation from [mLAT]")
    col_elev_to = df.columns.get_loc("Elevation to [mLAT]")
    col_diam_from = df.columns.get_loc("Diameter from [m]")
    col_diam_to = df.columns.get_loc("Diameter to [m]")
    if not isinstance(col_elev_from, int):
        raise ValueError("Expected scalar columns for elevation data.")
    if not isinstance(col_elev_to, int):
        raise ValueError("Expected scalar columns for elevation data.")
    if not isinstance(col_diam_from, int):
        raise ValueError("Expected scalar columns for diameter data.")
    if not isinstance(col_diam_to, int):
        raise ValueError("Expected scalar columns for diameter data.")
    elevation = [
        float(cast(float, df.iat[ind, col_elev_from])),
        float(cast(float, df.iat[ind, col_elev_to])),
    ]
    diameters = [
        float(cast(float, df.iat[ind, col_diam_from])),
        float(cast(float, df.iat[ind, col_diam_to])),
    ]
    df.loc[row_index, "Diameter" + _col + "[m]"] = float(
        np.interp(
            altitude_val,
            elevation,
            diameters,
        )
    )
    cols = ["Height [m]", "Volume [m3]", "Mass [t]", "rho [t/m]"]
    df.loc[df.index[ind], cols] = self.can_adjust_properties(df.iloc[ind])
    return df
assembly_tp_mp
assembly_tp_mp()

Process TP structural item to assembly with MP foundation.

Processes TP structural item to assembly with MP foundation ensuring continuity. TP skirt is processed as well.

Raises:

Type Description
TypeError

If TP or MP items need to be processed before.

Examples:

>>> from types import SimpleNamespace
>>> import pandas as pd
>>> helper = SimpleNamespace(
...     transition_piece=None,
...     monopile=None,
...     _init_spec_part=False,
... )
>>> OWT.assembly_tp_mp(helper)
Traceback (most recent call last):
    ...
TypeError: TP or MP items need to be processed before!
>>> tp = pd.DataFrame(
...     {
...         "Elevation from [mLAT]": [6.0, 0.0],
...         "Elevation to [mLAT]": [8.0, 4.0],
...         "Diameter from [m]": [6.0, 6.0],
...         "Diameter to [m]": [6.0, 6.0],
...         "Wall thickness [mm]": [10.0, 10.0],
...         "Volume [m3]": [5.0, 5.0],
...         "Mass [t]": [10.0, 10.0],
...         "rho [t/m]": [1.0, 1.0],
...     }
... )
>>> mp = pd.DataFrame(
...     {
...         "Elevation from [mLAT]": [0.0],
...         "Elevation to [mLAT]": [-10.0],
...         "Diameter from [m]": [6.0],
...         "Diameter to [m]": [6.0],
...         "Wall thickness [mm]": [10.0],
...         "Volume [m3]": [5.0],
...         "Mass [t]": [10.0],
...         "rho [t/m]": [1.0],
...     }
... )
>>> helper = SimpleNamespace(
...     transition_piece=tp,
...     monopile=mp,
...     pile_head=5.0,
...     substructure=None,
...     tp_skirt=None,
...     _init_spec_part=False,
... )
>>> helper.can_adjust_properties = OWT.can_adjust_properties
>>> helper.can_modification = lambda df, altitude, position="bottom": OWT.can_modification(
...     helper,
...     df,
...     altitude,
...     position=position,
... )
>>> OWT.assembly_tp_mp(helper)
>>> helper.substructure is not None
True
>>> helper.tp_skirt is not None
True
Source code in src/owi/metadatabase/geometry/processing.py
def assembly_tp_mp(self) -> None:
    """
    Process TP structural item to assembly with MP foundation.

    Processes TP structural item to assembly with MP foundation
    ensuring continuity. TP skirt is processed as well.

    Raises
    ------
    TypeError
        If TP or MP items need to be processed before.

    Examples
    --------
    >>> from types import SimpleNamespace
    >>> import pandas as pd
    >>> helper = SimpleNamespace(
    ...     transition_piece=None,
    ...     monopile=None,
    ...     _init_spec_part=False,
    ... )
    >>> OWT.assembly_tp_mp(helper)
    Traceback (most recent call last):
        ...
    TypeError: TP or MP items need to be processed before!
    >>> tp = pd.DataFrame(
    ...     {
    ...         "Elevation from [mLAT]": [6.0, 0.0],
    ...         "Elevation to [mLAT]": [8.0, 4.0],
    ...         "Diameter from [m]": [6.0, 6.0],
    ...         "Diameter to [m]": [6.0, 6.0],
    ...         "Wall thickness [mm]": [10.0, 10.0],
    ...         "Volume [m3]": [5.0, 5.0],
    ...         "Mass [t]": [10.0, 10.0],
    ...         "rho [t/m]": [1.0, 1.0],
    ...     }
    ... )
    >>> mp = pd.DataFrame(
    ...     {
    ...         "Elevation from [mLAT]": [0.0],
    ...         "Elevation to [mLAT]": [-10.0],
    ...         "Diameter from [m]": [6.0],
    ...         "Diameter to [m]": [6.0],
    ...         "Wall thickness [mm]": [10.0],
    ...         "Volume [m3]": [5.0],
    ...         "Mass [t]": [10.0],
    ...         "rho [t/m]": [1.0],
    ...     }
    ... )
    >>> helper = SimpleNamespace(
    ...     transition_piece=tp,
    ...     monopile=mp,
    ...     pile_head=5.0,
    ...     substructure=None,
    ...     tp_skirt=None,
    ...     _init_spec_part=False,
    ... )
    >>> helper.can_adjust_properties = OWT.can_adjust_properties
    >>> helper.can_modification = lambda df, altitude, position="bottom": OWT.can_modification(
    ...     helper,
    ...     df,
    ...     altitude,
    ...     position=position,
    ... )
    >>> OWT.assembly_tp_mp(helper)
    >>> helper.substructure is not None
    True
    >>> helper.tp_skirt is not None
    True
    """
    self._init_spec_part = True
    if (self.transition_piece is not None) and (self.monopile is not None):
        mp_head = self.pile_head
        tp = self.transition_piece
        df = deepcopy(tp.loc[tp["Elevation from [mLAT]"] > mp_head, :])
        if df.loc[df.index[0], "Elevation to [mLAT]"] != mp_head:
            # Not bolted connection (i.e. Rentel) preprocessing needed
            tp1 = self.can_modification(df, mp_head, position="bottom")
            self.substructure = pd.concat([tp1, deepcopy(self.monopile)])
        else:
            # Bolted connection, nothing to do
            self.substructure = pd.concat([df, deepcopy(self.monopile)])
        df = deepcopy(tp.loc[tp["Elevation to [mLAT]"] < mp_head, :])
        self.tp_skirt = self.can_modification(df, mp_head, position="top")
    else:
        raise TypeError("TP or MP items need to be processed before!")
assembly_full_structure
assembly_full_structure()

Process the full structure of the OWT.

Processes the full structure of the OWT: tower + tp combination with monopile.

Raises:

Type Description
TypeError

If tower or substructure needs to be processed before.

Examples:

>>> import pandas as pd
>>> from types import SimpleNamespace
>>> helper = SimpleNamespace(
...     substructure=pd.DataFrame({"Height [m]": [1.0]}),
...     tower=pd.DataFrame({"Height [m]": [2.0]}),
...     _init_spec_full=False,
... )
>>> OWT.assembly_full_structure(helper)
>>> float(helper.full_structure["Height [m]"].sum())
3.0
>>> helper._init_spec_full
True
>>> helper = SimpleNamespace(
...     substructure=None,
...     tower=None,
...     _init_spec_full=False,
... )
>>> OWT.assembly_full_structure(helper)
Traceback (most recent call last):
    ...
TypeError: Substructure needs to be processed before!
>>> helper = SimpleNamespace(
...     substructure=pd.DataFrame({"Height [m]": [1.0]}),
...     tower=None,
...     _init_spec_full=False,
... )
>>> OWT.assembly_full_structure(helper)
Traceback (most recent call last):
    ...
TypeError: Tower needs to be processed before!
Source code in src/owi/metadatabase/geometry/processing.py
def assembly_full_structure(self) -> None:
    """
    Process the full structure of the OWT.

    Processes the full structure of the OWT: tower + tp combination
    with monopile.

    Raises
    ------
    TypeError
        If tower or substructure needs to be processed before.

    Examples
    --------
    >>> import pandas as pd
    >>> from types import SimpleNamespace
    >>> helper = SimpleNamespace(
    ...     substructure=pd.DataFrame({"Height [m]": [1.0]}),
    ...     tower=pd.DataFrame({"Height [m]": [2.0]}),
    ...     _init_spec_full=False,
    ... )
    >>> OWT.assembly_full_structure(helper)
    >>> float(helper.full_structure["Height [m]"].sum())
    3.0
    >>> helper._init_spec_full
    True
    >>> helper = SimpleNamespace(
    ...     substructure=None,
    ...     tower=None,
    ...     _init_spec_full=False,
    ... )
    >>> OWT.assembly_full_structure(helper)
    Traceback (most recent call last):
        ...
    TypeError: Substructure needs to be processed before!
    >>> helper = SimpleNamespace(
    ...     substructure=pd.DataFrame({"Height [m]": [1.0]}),
    ...     tower=None,
    ...     _init_spec_full=False,
    ... )
    >>> OWT.assembly_full_structure(helper)
    Traceback (most recent call last):
        ...
    TypeError: Tower needs to be processed before!
    """
    self._init_spec_full = True
    if self.substructure is not None:
        if self.tower is not None:
            self.full_structure = pd.concat([self.tower, self.substructure])
        else:
            raise TypeError("Tower needs to be processed before!")
    else:
        raise TypeError("Substructure needs to be processed before!")
extend_dfs
extend_dfs()

Extend the dataframes with the subassembly columns.

Examples:

>>> import pandas as pd
>>> from types import SimpleNamespace
>>> helper = SimpleNamespace(
...     pile_toe=None,
...     rna=None,
...     tower=pd.DataFrame({"Height [m]": [1.0]}),
...     transition_piece=None,
...     monopile=None,
...     tw_lumped_mass=None,
...     tp_lumped_mass=None,
...     mp_lumped_mass=None,
...     tp_distributed_mass=None,
...     mp_distributed_mass=None,
...     grout=None,
...     sub_assemblies={},
...     substructure=None,
...     tp_skirt=None,
...     full_structure=None,
...     _init_spec_part=False,
...     _init_spec_full=False,
... )
>>> OWT.extend_dfs(helper)
>>> helper.tower["Subassembly"].iloc[0]
'TW'
>>> helper.tp_skirt is None
True
Source code in src/owi/metadatabase/geometry/processing.py
def extend_dfs(self) -> None:
    """
    Extend the dataframes with the subassembly columns.

    Examples
    --------
    >>> import pandas as pd
    >>> from types import SimpleNamespace
    >>> helper = SimpleNamespace(
    ...     pile_toe=None,
    ...     rna=None,
    ...     tower=pd.DataFrame({"Height [m]": [1.0]}),
    ...     transition_piece=None,
    ...     monopile=None,
    ...     tw_lumped_mass=None,
    ...     tp_lumped_mass=None,
    ...     mp_lumped_mass=None,
    ...     tp_distributed_mass=None,
    ...     mp_distributed_mass=None,
    ...     grout=None,
    ...     sub_assemblies={},
    ...     substructure=None,
    ...     tp_skirt=None,
    ...     full_structure=None,
    ...     _init_spec_part=False,
    ...     _init_spec_full=False,
    ... )
    >>> OWT.extend_dfs(helper)
    >>> helper.tower["Subassembly"].iloc[0]
    'TW'
    >>> helper.tp_skirt is None
    True
    """
    for attr in ATTR_PROC:
        df = getattr(self, attr)
        if df is not None:
            if "tower" in attr or "tw_" in attr or "rna" in attr:
                df["Subassembly"] = "TW"
                setattr(self, attr, df)
            elif "tp_" in attr or "transition" in attr or "grout" in attr:
                df["Subassembly"] = "TP"
                setattr(self, attr, df)
            elif "mp_" in attr or "monopile" in attr:
                df["Subassembly"] = "MP"
                setattr(self, attr, df)
    if "TP" in self.sub_assemblies and "MP" in self.sub_assemblies:
        self.assembly_tp_mp()
    else:
        self._init_spec_part = True
        self.tp_skirt = None
    if "TW" in self.sub_assemblies:
        self._init_spec_full = True
        if self.substructure is not None:
            self.assembly_full_structure()
        else:
            self.full_structure = None
    else:
        self.full_structure = None
        self._init_spec_full = True
transform_monopile_geometry
transform_monopile_geometry(cutoff_point=nan)

Return a dataframe with monopile geometry.

Returns a dataframe with the monopile geometry with the mudline as reference.

Parameters:

Name Type Description Default
cutoff_point floating

Depth from the mudline to cut the monopile geometry.

nan

Returns:

Type Description
DataFrame

Dataframe with the monopile geometry.

Raises:

Type Description
ValueError

If monopile subassembly data not found.

Source code in src/owi/metadatabase/geometry/processing.py
@typing.no_type_check
def transform_monopile_geometry(
    self,
    cutoff_point: np.floating = np.nan,
) -> pd.DataFrame:
    """
    Return a dataframe with monopile geometry.

    Returns a dataframe with the monopile geometry with the mudline
    as reference.

    Parameters
    ----------
    cutoff_point : np.floating, optional
        Depth from the mudline to cut the monopile geometry.

    Returns
    -------
    pd.DataFrame
        Dataframe with the monopile geometry.

    Raises
    ------
    ValueError
        If monopile subassembly data not found.
    """
    toe_depth_lat = self.sub_assemblies["MP"].position.z
    penetration = -((1e-3 * toe_depth_lat) - self.water_depth)
    pile = pd.DataFrame()
    if self.mp_sub_assemblies is not None:
        df = self.mp_sub_assemblies.copy()
    else:
        raise ValueError("Monopile subassembly data not found.")
    df.reset_index(inplace=True)
    for i, row in df.iterrows():
        if i != 0:
            pile.loc[i, "Elevation from [m]"] = penetration - 1e-3 * df["z"].iloc[i - 1]
            pile.loc[i, "Elevation to [m]"] = penetration - 1e-3 * row["z"]
            pile.loc[i, "Pile material"] = self.sub_assemblies["MP"].bb[0].material.title
            pile.loc[i, "Pile material submerged unit weight [kN/m3]"] = (
                1e-2 * self.sub_assemblies["MP"].bb[0].material.density - 10
            )
            pile.loc[i, "Wall thickness [mm]"] = row["wall_thickness"]
            bot_od = row["OD"].split("/")[0] if "/" in row["OD"] else row["OD"]
            top_od = row["OD"].split("/")[1] if "/" in row["OD"] else row["OD"]
            pile.loc[i, "Diameter [m]"] = 1e-3 * 0.5 * (float(bot_od) + float(top_od))
            pile.loc[i, "Youngs modulus [GPa]"] = self.sub_assemblies["MP"].bb[0].material.young_modulus
            pile.loc[i, "Poissons ratio [-]"] = self.sub_assemblies["MP"].bb[0].material.poisson_ratio
    if not np.isnan(cutoff_point):
        pile = pile.loc[pile["Elevation to [m]"] > cutoff_point].reset_index(drop=True)
        pile.loc[0, "Elevation from [m]"] = cutoff_point
    return pile
OWTs
OWTs(turbines, owts)

Class to process the geometry data of multiple OWTs.

:param owts: List of OWT objects. :param api: API object used to call get_* methods. :param materials: Pandas dataframe with the materials data. :param sub_assemblies: Dictionary of dictionaries of the subassemblies for each turbine. :param tower_base: Dictionary of the elevation of the OWT tower base in mLAT for each turbine. :param pile_head: Dictionary of the elevation of the pile head in mLAT for each turbine. :param water_depth: Dictionary of the water depth in mLAT for each turbine. :param tw_sub_assemblies: Dataframe of the tower subassemblies data from each turbine. :param tp_sub_assemblies: Dataframe of the transition piece subassemblies data from each turbine. :param mp_sub_assemblies: Dataframe of the monopile subassemblies data from each turbine. :param pile_toe: Dataframe of the elevation of the pile toe in mLAT from each turbine. :param rna: Dataframe of the RNA data from each turbine. :param tower: Dataframe of the tower data from each turbine. :param transition_piece: Dataframe of the transition piece data from each turbine. :param monopile: Dataframe of the monopile data from each turbine. :param tw_lumped_mass: Dataframe of the lumped masses data of the tower from each turbine. :param tp_lumped_mass: Dataframe of the lumped masses data of the transition piece from each turbine. :param mp_lumped_mass: Dataframe of the lumped masses data of the monopile from each turbine. :param tp_distributed_mass: Dataframe of the distributed masses data of the transition piece from each turbine. :param mp_distributed_mass: Dataframe of the distributed masses data of the monopile from each turbine. :param grout: Dataframe of the grout data from each turbine. :param full_structure: Dataframe of the full structure data from each turbine. :param tp_skirt: Dataframe of the transition piece skirt data from each turbine. :param substructure: Dataframe of the substructure data from each turbine. :param all_turbines: Dataframe of the general geometry data from each turbine. :param all_tubular_structures: Dataframe of the tubular structures data from each turbine. :param all_distributed_mass: Dataframe of the distributed masses data from each turbine. :param all_lumped_mass: Dataframe of the lumped masses data from each turbine.

Create an instance of the OWTs class with required parameters.

Parameters:

Name Type Description Default
turbines list of str

List of turbine titles.

required
owts list of OWT

List of OWT objects.

required

Examples:

>>> from types import SimpleNamespace
>>> stub = SimpleNamespace(
...     api="api",
...     materials="materials",
...     sub_assemblies={},
...     tower_base=0.0,
...     pile_head=0.0,
...     water_depth=0.0,
...     tw_sub_assemblies=None,
...     tp_sub_assemblies=None,
...     mp_sub_assemblies=None,
... )
>>> owts = OWTs(["T01"], [stub])
>>> owts.api
'api'
Source code in src/owi/metadatabase/geometry/processing.py
def __init__(
    self,
    turbines: list[str],
    owts: list[OWT],
) -> None:
    """
    Create an instance of the OWTs class with required parameters.

    Parameters
    ----------
    turbines : list of str
        List of turbine titles.
    owts : list of OWT
        List of OWT objects.

    Examples
    --------
    >>> from types import SimpleNamespace
    >>> stub = SimpleNamespace(
    ...     api="api",
    ...     materials="materials",
    ...     sub_assemblies={},
    ...     tower_base=0.0,
    ...     pile_head=0.0,
    ...     water_depth=0.0,
    ...     tw_sub_assemblies=None,
    ...     tp_sub_assemblies=None,
    ...     mp_sub_assemblies=None,
    ... )
    >>> owts = OWTs(["T01"], [stub])
    >>> owts.api
    'api'
    """
    self.owts = dict(zip(turbines, owts))
    self.api = self.owts[turbines[0]].api
    self.materials = self.owts[turbines[0]].materials
    for attr in [
        "sub_assemblies",
        "tower_base",
        "pile_head",
        "water_depth",
    ]:
        dict_ = {k: getattr(owt, attr) for k, owt in zip(turbines, self.owts.values())}
        setattr(self, attr, dict_)
    for attr in [
        "tw_sub_assemblies",
        "tp_sub_assemblies",
        "mp_sub_assemblies",
    ]:
        sa_turb_list = [getattr(owt, attr) for owt in self.owts.values() if getattr(owt, attr) is not None]
        df = None if sa_turb_list == [] else pd.concat(sa_turb_list)
        setattr(self, attr, df)
    for attr in ATTR_PROC:
        setattr(self, attr, [])
    for attr in ATTR_SPEC:
        setattr(self, attr, [])
    for attr in ATTR_FULL:
        setattr(self, attr, [])
    self._init = False
Functions
process_structures
process_structures()

Set dataframes with required properties to model the tower.

Sets dataframes containing the required properties to model the tower geometry, including the RNA system.

Examples:

>>> from types import SimpleNamespace
>>> from unittest import mock
>>> stub = SimpleNamespace(
...     api="api",
...     materials="materials",
...     sub_assemblies={"TW": 1, "TP": 1, "MP": 1},
...     tower_base=0.0,
...     pile_head=0.0,
...     water_depth=0.0,
...     tw_sub_assemblies=None,
...     tp_sub_assemblies=None,
...     mp_sub_assemblies=None,
...     process_structure=lambda *args, **kwargs: None,
...     extend_dfs=lambda *args, **kwargs: None,
...     pile_toe=0.0,
...     rna=None,
...     tower=None,
...     transition_piece=None,
...     monopile=None,
...     tw_lumped_mass=None,
...     tp_lumped_mass=None,
...     mp_lumped_mass=None,
...     tp_distributed_mass=None,
...     mp_distributed_mass=None,
...     grout=None,
...     full_structure=None,
...     tp_skirt=None,
...     substructure=None,
...     all_tubular_structures=None,
...     all_distributed_mass=None,
...     all_lumped_mass=None,
...     all_turbines=None,
... )
>>> owts = OWTs(["T01"], [stub])
>>> with mock.patch.object(OWTs, "_concat_list", lambda self, attrs: None), mock.patch.object(
...     OWTs, "_assembly_turbine", lambda self: None
... ):
...     owts.process_structures()
>>> owts._init
True
Source code in src/owi/metadatabase/geometry/processing.py
def process_structures(self) -> None:
    """
    Set dataframes with required properties to model the tower.

    Sets dataframes containing the required properties to model the
    tower geometry, including the RNA system.

    Examples
    --------
    >>> from types import SimpleNamespace
    >>> from unittest import mock
    >>> stub = SimpleNamespace(
    ...     api="api",
    ...     materials="materials",
    ...     sub_assemblies={"TW": 1, "TP": 1, "MP": 1},
    ...     tower_base=0.0,
    ...     pile_head=0.0,
    ...     water_depth=0.0,
    ...     tw_sub_assemblies=None,
    ...     tp_sub_assemblies=None,
    ...     mp_sub_assemblies=None,
    ...     process_structure=lambda *args, **kwargs: None,
    ...     extend_dfs=lambda *args, **kwargs: None,
    ...     pile_toe=0.0,
    ...     rna=None,
    ...     tower=None,
    ...     transition_piece=None,
    ...     monopile=None,
    ...     tw_lumped_mass=None,
    ...     tp_lumped_mass=None,
    ...     mp_lumped_mass=None,
    ...     tp_distributed_mass=None,
    ...     mp_distributed_mass=None,
    ...     grout=None,
    ...     full_structure=None,
    ...     tp_skirt=None,
    ...     substructure=None,
    ...     all_tubular_structures=None,
    ...     all_distributed_mass=None,
    ...     all_lumped_mass=None,
    ...     all_turbines=None,
    ... )
    >>> owts = OWTs(["T01"], [stub])
    >>> with mock.patch.object(OWTs, "_concat_list", lambda self, attrs: None), mock.patch.object(
    ...     OWTs, "_assembly_turbine", lambda self: None
    ... ):
    ...     owts.process_structures()
    >>> owts._init
    True
    """
    attr_list = ATTR_PROC + ATTR_SPEC + ATTR_FULL
    attr_list.remove("all_turbines")
    if self._init:
        return
    self._init = True
    for owt in self.owts.values():
        if len(owt.sub_assemblies) != 3:
            for sa in owt.sub_assemblies.keys():  # noqa: SIM118
                owt.process_structure(option=sa)
        else:
            owt.process_structure()
        owt.extend_dfs()
        for attr in attr_list:
            if attr == "pile_toe":
                pile_toe_list = cast(list[Union[np.float64, float, None]], self.pile_toe)
                pile_toe_list.append(getattr(owt, attr))
                self.pile_toe = pile_toe_list
            elif attr == "all_tubular_structures":
                self.all_tubular_structures.extend([owt.tower, owt.transition_piece, owt.monopile])
            elif attr == "all_distributed_mass":
                self.all_distributed_mass.extend(
                    [
                        owt.tp_distributed_mass,
                        owt.grout,
                        owt.mp_distributed_mass,
                    ]
                )
            elif attr == "all_lumped_mass":
                if isinstance(owt.rna, pd.DataFrame):
                    cols = [
                        "X [m]",
                        "Y [m]",
                        "Z [mLAT]",
                        "Mass [t]",
                        "Description",
                        "Subassembly",
                    ]
                    rna_ = owt.rna[cols]
                else:
                    rna_ = owt.rna
                self.all_lumped_mass.extend(
                    [
                        rna_,
                        owt.tw_lumped_mass,
                        owt.tp_lumped_mass,
                        owt.mp_lumped_mass,
                    ]
                )
            else:
                attr_val = getattr(self, attr)
                owt_attr_val = getattr(owt, attr)
                attr_val.append(owt_attr_val)
    attr_list.remove("pile_toe")
    self.pile_toe = dict(zip(self.owts.keys(), self.pile_toe))
    self._concat_list(attr_list)
    self._assembly_turbine()
select_owt
select_owt(turbine)

Select OWT object from the OWTs object.

Parameters:

Name Type Description Default
turbine str or int

Title of the turbine or its index in the original list of turbine titles (from get method).

required

Returns:

Type Description
OWT

OWT object.

Raises:

Type Description
ValueError

If turbine must be specified as single turbine title or its index from the get method input turbine list.

Examples:

>>> from types import SimpleNamespace
>>> stub = SimpleNamespace(
...     api="api",
...     materials="materials",
...     sub_assemblies={},
...     tower_base=0.0,
...     pile_head=0.0,
...     water_depth=0.0,
...     tw_sub_assemblies=None,
...     tp_sub_assemblies=None,
...     mp_sub_assemblies=None,
... )
>>> owts = OWTs(["T01"], [stub])
>>> owts.select_owt("T01") is stub
True
Source code in src/owi/metadatabase/geometry/processing.py
def select_owt(self, turbine: Union[str, int]) -> OWT:
    """
    Select OWT object from the OWTs object.

    Parameters
    ----------
    turbine : str or int
        Title of the turbine or its index in the original list of
        turbine titles (from get method).

    Returns
    -------
    OWT
        OWT object.

    Raises
    ------
    ValueError
        If turbine must be specified as single turbine title or
        its index from the get method input turbine list.

    Examples
    --------
    >>> from types import SimpleNamespace
    >>> stub = SimpleNamespace(
    ...     api="api",
    ...     materials="materials",
    ...     sub_assemblies={},
    ...     tower_base=0.0,
    ...     pile_head=0.0,
    ...     water_depth=0.0,
    ...     tw_sub_assemblies=None,
    ...     tp_sub_assemblies=None,
    ...     mp_sub_assemblies=None,
    ... )
    >>> owts = OWTs(["T01"], [stub])
    >>> owts.select_owt("T01") is stub
    True
    """
    if isinstance(turbine, int):
        return self.owts[list(self.owts.keys())[turbine]]
    elif isinstance(turbine, str):
        return self.owts[turbine]
    else:
        raise ValueError(
            "You must specify a single turbine title or \
            its index from the the get method input turbine list."
        )

Functions

geometry.structures — Data Structures

structures

Module containing the data classes for the geometry module.

Classes

BaseStructure

Base class for all structures.

Material
Material(json)

Bases: BaseStructure

Material derived from the raw data.

Create an instance of the Material class with required parameters.

Parameters:

Name Type Description Default
json DataMat

JSON data containing the material information.

required

Examples:

>>> data = {
...     "title": "Steel",
...     "slug": "steel",
...     "id": np.int64(1),
...     "description": "",
...     "young_modulus": np.float64(210000.0),
...     "density": np.float64(7850.0),
...     "poisson_ratio": np.float64(0.3),
... }
>>> material = Material(data)
>>> material.title
'Steel'
Source code in src/owi/metadatabase/geometry/structures.py
def __init__(self, json: DataMat) -> None:
    """
    Create an instance of the Material class with required
    parameters.

    Parameters
    ----------
    json : DataMat
        JSON data containing the material information.

    Examples
    --------
    >>> data = {
    ...     "title": "Steel",
    ...     "slug": "steel",
    ...     "id": np.int64(1),
    ...     "description": "",
    ...     "young_modulus": np.float64(210000.0),
    ...     "density": np.float64(7850.0),
    ...     "poisson_ratio": np.float64(0.3),
    ... }
    >>> material = Material(data)
    >>> material.title
    'Steel'
    """
    self.title = json["title"]
    self.description = json["description"]
    self.density = json["density"]
    self.poisson_ratio = json["poisson_ratio"]
    self.young_modulus = json["young_modulus"]
    self.id = json["id"]
Functions
as_dict
as_dict()

Transform data into dictionary.

Returns:

Type Description
dict

Dictionary with the following keys:

  • "title": Name of the material.
  • "description": Description of the material.
  • "poisson_ratio": Poisson ratio of the material.
  • "young_modulus": Young modulus of the material.

Examples:

>>> data = {
...     "title": "Steel",
...     "slug": "steel",
...     "id": np.int64(1),
...     "description": "",
...     "young_modulus": np.float64(210000.0),
...     "density": np.float64(7850.0),
...     "poisson_ratio": np.float64(0.3),
... }
>>> Material(data).as_dict()["title"]
'Steel'
Source code in src/owi/metadatabase/geometry/structures.py
def as_dict(self) -> dict[str, Union[str, np.float64]]:
    """
    Transform data into dictionary.

    Returns
    -------
    dict
        Dictionary with the following keys:

        - "title": Name of the material.
        - "description": Description of the material.
        - "poisson_ratio": Poisson ratio of the material.
        - "young_modulus": Young modulus of the material.

    Examples
    --------
    >>> data = {
    ...     "title": "Steel",
    ...     "slug": "steel",
    ...     "id": np.int64(1),
    ...     "description": "",
    ...     "young_modulus": np.float64(210000.0),
    ...     "density": np.float64(7850.0),
    ...     "poisson_ratio": np.float64(0.3),
    ... }
    >>> Material(data).as_dict()["title"]
    'Steel'
    """
    return {
        "title": self.title,
        "description": self.description,
        "poisson_ratio": self.poisson_ratio,
        "young_modulus": self.young_modulus,
    }
Position
Position(
    x=DEFAULT_NP_FLOAT64_VALUE,
    y=DEFAULT_NP_FLOAT64_VALUE,
    z=DEFAULT_NP_FLOAT64_VALUE,
    alpha=DEFAULT_NP_FLOAT64_VALUE,
    beta=DEFAULT_NP_FLOAT64_VALUE,
    gamma=DEFAULT_NP_FLOAT64_VALUE,
    reference_system="LAT",
)

Bases: BaseStructure

Position of the components.

Create an instance of the Position class with required parameters.

Parameters:

Name Type Description Default
x float64

X coordinate of the component.

DEFAULT_NP_FLOAT64_VALUE
y float64

Y coordinate of the component.

DEFAULT_NP_FLOAT64_VALUE
z float64

Z coordinate of the component.

DEFAULT_NP_FLOAT64_VALUE
alpha float64

Rotation around the x-axis.

DEFAULT_NP_FLOAT64_VALUE
beta float64

Rotation around the y-axis.

DEFAULT_NP_FLOAT64_VALUE
gamma float64

Rotation around the z-axis.

DEFAULT_NP_FLOAT64_VALUE
reference_system str

Reference system for the vertical position, default is "LAT".

'LAT'

Examples:

>>> pos = Position(np.float64(1), np.float64(2), np.float64(3))
>>> tuple(map(float, (pos.x, pos.y, pos.z)))
(1.0, 2.0, 3.0)
Source code in src/owi/metadatabase/geometry/structures.py
def __init__(
    self,
    x: np.float64 = DEFAULT_NP_FLOAT64_VALUE,
    y: np.float64 = DEFAULT_NP_FLOAT64_VALUE,
    z: np.float64 = DEFAULT_NP_FLOAT64_VALUE,
    alpha: np.float64 = DEFAULT_NP_FLOAT64_VALUE,
    beta: np.float64 = DEFAULT_NP_FLOAT64_VALUE,
    gamma: np.float64 = DEFAULT_NP_FLOAT64_VALUE,
    reference_system: str = "LAT",
) -> None:
    """
    Create an instance of the Position class with required
    parameters.

    Parameters
    ----------
    x : np.float64, optional
        X coordinate of the component.
    y : np.float64, optional
        Y coordinate of the component.
    z : np.float64, optional
        Z coordinate of the component.
    alpha : np.float64, optional
        Rotation around the x-axis.
    beta : np.float64, optional
        Rotation around the y-axis.
    gamma : np.float64, optional
        Rotation around the z-axis.
    reference_system : str, optional
        Reference system for the vertical position, default is
        "LAT".

    Examples
    --------
    >>> pos = Position(np.float64(1), np.float64(2), np.float64(3))
    >>> tuple(map(float, (pos.x, pos.y, pos.z)))
    (1.0, 2.0, 3.0)
    """
    self.x = x
    self.y = y
    self.z = z
    self.alpha = alpha
    self.beta = beta
    self.gamma = gamma
    self.reference_system = reference_system
Functions
BuildingBlock
BuildingBlock(json, subassembly=None)

Bases: BaseStructure

Building blocks description.

Create an instance of the BuildingBlock class with required parameters.

Parameters:

Name Type Description Default
json DataBB

JSON data containing the building block information.

required
subassembly Any

Subassembly object containing the building block.

None

Examples:

>>> data = {
...     "id": np.int64(1),
...     "description": "",
...     "slug": "bb",
...     "alpha": np.float64(0),
...     "beta": np.float64(0),
...     "gamma": np.float64(0),
...     "x_position": np.float64(0),
...     "y_position": np.float64(0),
...     "z_position": np.float64(0),
...     "vertical_position_reference_system": "LAT",
...     "title": "BB_1",
...     "height": np.float64(0),
...     "mass_distribution": np.float64(np.nan),
...     "volume_distribution": np.float64(np.nan),
...     "area_distribution": np.float64(np.nan),
...     "c_d": np.float64(np.nan),
...     "c_m": np.float64(np.nan),
...     "sub_assembly": np.int64(1),
...     "projectsite_name": "Site",
...     "asset_name": "T01",
...     "subassembly_name": "SA",
...     "material_name": "Steel",
...     "youngs_modulus": np.float64(np.nan),
...     "density": np.float64(np.nan),
...     "poissons_ratio": np.float64(np.nan),
...     "bottom_outer_diameter": np.float64(np.nan),
...     "top_outer_diameter": np.float64(np.nan),
...     "wall_thickness": np.float64(np.nan),
...     "material": np.float64(np.nan),
...     "moment_of_inertia_x": np.float64(1.0),
...     "moment_of_inertia_y": np.float64(2.0),
...     "moment_of_inertia_z": np.float64(3.0),
...     "mass": np.float64(100.0),
... }
>>> BuildingBlock(data).title
'BB_1'
Source code in src/owi/metadatabase/geometry/structures.py
def __init__(self, json: DataBB, subassembly: Union[Any, None] = None) -> None:
    """
    Create an instance of the BuildingBlock class with required
    parameters.

    Parameters
    ----------
    json : DataBB
        JSON data containing the building block information.
    subassembly : Any, optional
        Subassembly object containing the building block.

    Examples
    --------
    >>> data = {
    ...     "id": np.int64(1),
    ...     "description": "",
    ...     "slug": "bb",
    ...     "alpha": np.float64(0),
    ...     "beta": np.float64(0),
    ...     "gamma": np.float64(0),
    ...     "x_position": np.float64(0),
    ...     "y_position": np.float64(0),
    ...     "z_position": np.float64(0),
    ...     "vertical_position_reference_system": "LAT",
    ...     "title": "BB_1",
    ...     "height": np.float64(0),
    ...     "mass_distribution": np.float64(np.nan),
    ...     "volume_distribution": np.float64(np.nan),
    ...     "area_distribution": np.float64(np.nan),
    ...     "c_d": np.float64(np.nan),
    ...     "c_m": np.float64(np.nan),
    ...     "sub_assembly": np.int64(1),
    ...     "projectsite_name": "Site",
    ...     "asset_name": "T01",
    ...     "subassembly_name": "SA",
    ...     "material_name": "Steel",
    ...     "youngs_modulus": np.float64(np.nan),
    ...     "density": np.float64(np.nan),
    ...     "poissons_ratio": np.float64(np.nan),
    ...     "bottom_outer_diameter": np.float64(np.nan),
    ...     "top_outer_diameter": np.float64(np.nan),
    ...     "wall_thickness": np.float64(np.nan),
    ...     "material": np.float64(np.nan),
    ...     "moment_of_inertia_x": np.float64(1.0),
    ...     "moment_of_inertia_y": np.float64(2.0),
    ...     "moment_of_inertia_z": np.float64(3.0),
    ...     "mass": np.float64(100.0),
    ... }
    >>> BuildingBlock(data).title
    'BB_1'
    """
    json_data = dict(json)
    self.id = json_data["id"]
    self.title = json_data["title"]
    description = json_data.get("description")
    if description is None or pd.isna(description):
        self.description = ""
        json_data["description"] = None
    else:
        self.description = str(description)
    self.position = Position(
        x=json_data["x_position"],
        y=json_data["y_position"],
        z=json_data["z_position"],
        alpha=json_data["alpha"],
        beta=json_data["beta"],
        gamma=json_data["gamma"],
        reference_system=json_data["vertical_position_reference_system"],
    )
    self.material = None
    if "material" in json_data and subassembly:
        material_id = json_data["material"]
        if material_id and not np.isnan(material_id):
            for mat in subassembly.materials:
                if np.int64(mat.id) == np.int64(material_id):
                    self.material = mat
                    break
    self.json = cast(DataBB, json_data)
Attributes
type property
type

Type of the building block.

Examples:

>>> data = {
...     "id": np.int64(1),
...     "description": "",
...     "slug": "bb",
...     "alpha": np.float64(0),
...     "beta": np.float64(0),
...     "gamma": np.float64(0),
...     "x_position": np.float64(0),
...     "y_position": np.float64(0),
...     "z_position": np.float64(0),
...     "vertical_position_reference_system": "LAT",
...     "title": "BB_1",
...     "height": np.float64(0),
...     "mass_distribution": np.float64(np.nan),
...     "volume_distribution": np.float64(np.nan),
...     "area_distribution": np.float64(np.nan),
...     "c_d": np.float64(np.nan),
...     "c_m": np.float64(np.nan),
...     "sub_assembly": np.int64(1),
...     "projectsite_name": "Site",
...     "asset_name": "T01",
...     "subassembly_name": "SA",
...     "material_name": "Steel",
...     "youngs_modulus": np.float64(np.nan),
...     "density": np.float64(np.nan),
...     "poissons_ratio": np.float64(np.nan),
...     "bottom_outer_diameter": np.float64(np.nan),
...     "top_outer_diameter": np.float64(np.nan),
...     "wall_thickness": np.float64(np.nan),
...     "material": np.float64(np.nan),
...     "moment_of_inertia_x": np.float64(1.0),
...     "moment_of_inertia_y": np.float64(2.0),
...     "moment_of_inertia_z": np.float64(3.0),
...     "mass": np.float64(100.0),
... }
>>> BuildingBlock(data).type
'lumped_mass'
wall_thickness property
wall_thickness

Wall thickness of the building block (if exists), mm.

bottom_outer_diameter property
bottom_outer_diameter

Bottom outer diameter of the building block (if exists), mm.

top_outer_diameter property
top_outer_diameter

Top outer diameter of the building block (if exists), mm.

diameter_str property
diameter_str

Diameter of the building block as a string (if exists), mm.

height property
height

Height of the building block , mm.

volume property
volume

Volume of the building block, m³.

mass property
mass

Mass of the building block, kg.

Examples:

>>> data = {
...     "id": np.int64(1),
...     "description": "",
...     "slug": "bb",
...     "alpha": np.float64(0),
...     "beta": np.float64(0),
...     "gamma": np.float64(0),
...     "x_position": np.float64(0),
...     "y_position": np.float64(0),
...     "z_position": np.float64(0),
...     "vertical_position_reference_system": "LAT",
...     "title": "BB_1",
...     "height": np.float64(0),
...     "mass_distribution": np.float64(np.nan),
...     "volume_distribution": np.float64(np.nan),
...     "area_distribution": np.float64(np.nan),
...     "c_d": np.float64(np.nan),
...     "c_m": np.float64(np.nan),
...     "sub_assembly": np.int64(1),
...     "projectsite_name": "Site",
...     "asset_name": "T01",
...     "subassembly_name": "SA",
...     "material_name": "Steel",
...     "youngs_modulus": np.float64(np.nan),
...     "density": np.float64(np.nan),
...     "poissons_ratio": np.float64(np.nan),
...     "bottom_outer_diameter": np.float64(np.nan),
...     "top_outer_diameter": np.float64(np.nan),
...     "wall_thickness": np.float64(np.nan),
...     "material": np.float64(np.nan),
...     "moment_of_inertia_x": np.float64(1.0),
...     "moment_of_inertia_y": np.float64(2.0),
...     "moment_of_inertia_z": np.float64(3.0),
...     "mass": np.float64(100.0),
... }
>>> float(BuildingBlock(data).mass)
100.0
moment_of_inertia property
moment_of_inertia

Moment of inertia of the building block, kg*m².

IMPORTANT! Only works for building blocks of the type lumped_mass.

Returns:

Type Description
dict

Dictionary containing the moment of inertia around the three axes, x, y, z.

Examples:

>>> data = {
...     "id": np.int64(1),
...     "description": "",
...     "slug": "bb",
...     "alpha": np.float64(0),
...     "beta": np.float64(0),
...     "gamma": np.float64(0),
...     "x_position": np.float64(0),
...     "y_position": np.float64(0),
...     "z_position": np.float64(0),
...     "vertical_position_reference_system": "LAT",
...     "title": "BB_1",
...     "height": np.float64(0),
...     "mass_distribution": np.float64(np.nan),
...     "volume_distribution": np.float64(np.nan),
...     "area_distribution": np.float64(np.nan),
...     "c_d": np.float64(np.nan),
...     "c_m": np.float64(np.nan),
...     "sub_assembly": np.int64(1),
...     "projectsite_name": "Site",
...     "asset_name": "T01",
...     "subassembly_name": "SA",
...     "material_name": "Steel",
...     "youngs_modulus": np.float64(np.nan),
...     "density": np.float64(np.nan),
...     "poissons_ratio": np.float64(np.nan),
...     "bottom_outer_diameter": np.float64(np.nan),
...     "top_outer_diameter": np.float64(np.nan),
...     "wall_thickness": np.float64(np.nan),
...     "material": np.float64(np.nan),
...     "moment_of_inertia_x": np.float64(1.0),
...     "moment_of_inertia_y": np.float64(2.0),
...     "moment_of_inertia_z": np.float64(3.0),
...     "mass": np.float64(100.0),
... }
>>> float(BuildingBlock(data).moment_of_inertia["y"])
2.0
outline property
outline

Trace of the outlines.

Returns:

Type Description
tuple or None

A tuple of two lists containing the x and corresponding z coordinates of the outline, or None if not applicable.

marker property
marker

Indication for the lumped mass in the building block.

Returns:

Type Description
dict or None

Dictionary containing the x, y, z coordinates of the marker and the radius of the marker, or None if not applicable.

line property
line

Line for the distributed mass in the building block.

Returns:

Type Description
dict or None

Dictionary containing the x, y, z coordinates of the line and the color of the line, or None if not applicable.

Functions
as_dict
as_dict()

Transform data into dictionary.

Returns:

Type Description
dict

Dictionary with the following keys:

  • "title": Name of the building block.
  • "x": X coordinate of the building block.
  • "y": Y coordinate of the building block.
  • "z": Z coordinate of the building block.
  • "OD": Outer diameter of the building block.
  • "wall_thickness": Wall thickness of the building block.
  • "height": Height of the building block.
  • "volume": Volume of the building block.
  • "mass": Mass of the building block.
  • "moment_of_inertia": Moment of inertia of the building block.
  • "description": Description of the building block.
Source code in src/owi/metadatabase/geometry/structures.py
def as_dict(
    self,
) -> dict[str, Union[str, np.float64, dict[str, Union[np.float64, None]], None]]:
    """
    Transform data into dictionary.

    Returns
    -------
    dict
        Dictionary with the following keys:

        - "title": Name of the building block.
        - "x": X coordinate of the building block.
        - "y": Y coordinate of the building block.
        - "z": Z coordinate of the building block.
        - "OD": Outer diameter of the building block.
        - "wall_thickness": Wall thickness of the building block.
        - "height": Height of the building block.
        - "volume": Volume of the building block.
        - "mass": Mass of the building block.
        - "moment_of_inertia": Moment of inertia of the building
          block.
        - "description": Description of the building block.
    """
    return {
        "title": self.title,
        "x": self.position.x,
        "y": self.position.y,
        "z": self.position.z,
        "OD": self.diameter_str,
        "wall_thickness": self.wall_thickness,
        "height": self.height,
        "volume": self.volume,
        "mass": self.mass,
        "moment_of_inertia": self.moment_of_inertia,
        "description": self.description,
    }
__str__
__str__()

Examples:

>>> data = {
...     "id": np.int64(1),
...     "description": "",
...     "slug": "bb",
...     "alpha": np.float64(0),
...     "beta": np.float64(0),
...     "gamma": np.float64(0),
...     "x_position": np.float64(0),
...     "y_position": np.float64(0),
...     "z_position": np.float64(0),
...     "vertical_position_reference_system": "LAT",
...     "title": "BB_1",
...     "height": np.float64(0),
...     "mass_distribution": np.float64(np.nan),
...     "volume_distribution": np.float64(np.nan),
...     "area_distribution": np.float64(np.nan),
...     "c_d": np.float64(np.nan),
...     "c_m": np.float64(np.nan),
...     "sub_assembly": np.int64(1),
...     "projectsite_name": "Site",
...     "asset_name": "T01",
...     "subassembly_name": "SA",
...     "material_name": "Steel",
...     "youngs_modulus": np.float64(np.nan),
...     "density": np.float64(np.nan),
...     "poissons_ratio": np.float64(np.nan),
...     "bottom_outer_diameter": np.float64(np.nan),
...     "top_outer_diameter": np.float64(np.nan),
...     "wall_thickness": np.float64(np.nan),
...     "material": np.float64(np.nan),
...     "moment_of_inertia_x": np.float64(1.0),
...     "moment_of_inertia_y": np.float64(2.0),
...     "moment_of_inertia_z": np.float64(3.0),
...     "mass": np.float64(100.0),
... }
>>> str(BuildingBlock(data))
'BB_1 (lumped_mass)'
Source code in src/owi/metadatabase/geometry/structures.py
def __str__(self) -> str:
    """
    Examples
    --------
    >>> data = {
    ...     "id": np.int64(1),
    ...     "description": "",
    ...     "slug": "bb",
    ...     "alpha": np.float64(0),
    ...     "beta": np.float64(0),
    ...     "gamma": np.float64(0),
    ...     "x_position": np.float64(0),
    ...     "y_position": np.float64(0),
    ...     "z_position": np.float64(0),
    ...     "vertical_position_reference_system": "LAT",
    ...     "title": "BB_1",
    ...     "height": np.float64(0),
    ...     "mass_distribution": np.float64(np.nan),
    ...     "volume_distribution": np.float64(np.nan),
    ...     "area_distribution": np.float64(np.nan),
    ...     "c_d": np.float64(np.nan),
    ...     "c_m": np.float64(np.nan),
    ...     "sub_assembly": np.int64(1),
    ...     "projectsite_name": "Site",
    ...     "asset_name": "T01",
    ...     "subassembly_name": "SA",
    ...     "material_name": "Steel",
    ...     "youngs_modulus": np.float64(np.nan),
    ...     "density": np.float64(np.nan),
    ...     "poissons_ratio": np.float64(np.nan),
    ...     "bottom_outer_diameter": np.float64(np.nan),
    ...     "top_outer_diameter": np.float64(np.nan),
    ...     "wall_thickness": np.float64(np.nan),
    ...     "material": np.float64(np.nan),
    ...     "moment_of_inertia_x": np.float64(1.0),
    ...     "moment_of_inertia_y": np.float64(2.0),
    ...     "moment_of_inertia_z": np.float64(3.0),
    ...     "mass": np.float64(100.0),
    ... }
    >>> str(BuildingBlock(data))
    'BB_1 (lumped_mass)'
    """
    return self.title + " (" + self.type + ")"
SubAssembly
SubAssembly(materials, json, api_object=None)

Bases: BaseStructure

Subassemblies description.

Create an instance of the SubAssembly class with required parameters.

Parameters:

Name Type Description Default
materials DataFrame or bool or int64 or None

Pandas dataframe containing the material information.

required
json DataSA

JSON data containing the subassembly information.

required
api_object Any

API object to access the building blocks.

None

Examples:

>>> materials = pd.DataFrame([
...     {
...         "title": "Steel",
...         "slug": "steel",
...         "id": np.int64(1),
...         "description": "",
...         "young_modulus": np.float64(210000.0),
...         "density": np.float64(7850.0),
...         "poisson_ratio": np.float64(0.3),
...     }
... ])
>>> data = {
...     "id": np.int64(1),
...     "title": "SA_1",
...     "description": "",
...     "slug": "sa",
...     "x_position": np.float64(0),
...     "y_position": np.float64(0),
...     "z_position": np.float64(0),
...     "vertical_position_reference_system": "LAT",
...     "subassembly_type": "TW",
...     "source": "api",
...     "asset": np.int64(1),
...     "model_definition": np.int64(1),
... }
>>> sa = SubAssembly(materials, data)
>>> sa.title
'SA_1'
Source code in src/owi/metadatabase/geometry/structures.py
def __init__(
    self,
    materials: Union[pd.DataFrame, bool, np.int64, None],
    json: DataSA,
    api_object: Union[Any, None] = None,
) -> None:
    """
    Create an instance of the SubAssembly class with required
    parameters.

    Parameters
    ----------
    materials : pd.DataFrame or bool or np.int64 or None
        Pandas dataframe containing the material information.
    json : DataSA
        JSON data containing the subassembly information.
    api_object : Any, optional
        API object to access the building blocks.

    Examples
    --------
    >>> materials = pd.DataFrame([
    ...     {
    ...         "title": "Steel",
    ...         "slug": "steel",
    ...         "id": np.int64(1),
    ...         "description": "",
    ...         "young_modulus": np.float64(210000.0),
    ...         "density": np.float64(7850.0),
    ...         "poisson_ratio": np.float64(0.3),
    ...     }
    ... ])
    >>> data = {
    ...     "id": np.int64(1),
    ...     "title": "SA_1",
    ...     "description": "",
    ...     "slug": "sa",
    ...     "x_position": np.float64(0),
    ...     "y_position": np.float64(0),
    ...     "z_position": np.float64(0),
    ...     "vertical_position_reference_system": "LAT",
    ...     "subassembly_type": "TW",
    ...     "source": "api",
    ...     "asset": np.int64(1),
    ...     "model_definition": np.int64(1),
    ... }
    >>> sa = SubAssembly(materials, data)
    >>> sa.title
    'SA_1'
    """
    json_data = dict(json)
    self.api = api_object
    self.id = json_data["id"]
    self.title = json_data["title"]
    description = json_data.get("description")
    self.description = description
    self.position = Position(
        x=json_data["x_position"],
        y=json_data["y_position"],
        z=json_data["z_position"],
        reference_system=json_data["vertical_position_reference_system"],
    )
    self.type = json_data["subassembly_type"]
    self.source = json_data["source"]
    self.asset = json_data["asset"]
    self.bb = None
    materials_df = cast(pd.DataFrame, materials)
    self.materials = [Material(cast(DataMat, m.to_dict())) for _, m in materials_df.iterrows()]
Attributes
color property
color

Color based on subassembly type.

Examples:

>>> materials = pd.DataFrame([
...     {
...         "title": "Steel",
...         "slug": "steel",
...         "id": np.int64(1),
...         "description": "",
...         "young_modulus": np.float64(210000.0),
...         "density": np.float64(7850.0),
...         "poisson_ratio": np.float64(0.3),
...     }
... ])
>>> data = {
...     "id": np.int64(1),
...     "title": "SA_1",
...     "description": "",
...     "slug": "sa",
...     "x_position": np.float64(0),
...     "y_position": np.float64(0),
...     "z_position": np.float64(0),
...     "vertical_position_reference_system": "LAT",
...     "subassembly_type": "TW",
...     "source": "api",
...     "asset": np.int64(1),
...     "model_definition": np.int64(1),
... }
>>> SubAssembly(materials, data).color
'grey'
building_blocks property
building_blocks

Building blocks of the subassembly.

Returns:

Type Description
list of BuildingBlock or None

List of instances of building block class.

Raises:

Type Description
ValueError

If no API configured or no building blocks found.

height property
height

Height of the subassembly.

mass property
mass

Mass of the subassembly.

properties property
properties

Mass and height of the subassembly.

outline property
outline

Defines the traces of the outline of the subassembly.

Returns:

Type Description
tuple

A tuple of two lists containing the x and corresponding z coordinates of the outline.

absolute_bottom property
absolute_bottom

Absolute bottom of the subassembly, m.

absolute_top property
absolute_top

Absolute top of the subassembly, m.

Functions
plot
plot(x_offset=DEFAULT_NP_FLOAT64_VALUE)

Plot the subassembly.

Source code in src/owi/metadatabase/geometry/structures.py
def plot(self, x_offset: np.float64 = DEFAULT_NP_FLOAT64_VALUE) -> None:
    """Plot the subassembly."""
    x0, z = self.outline
    plt.plot(
        [x + x_offset for x in x0],
        z,
        color=PLOT_SETTINGS_SUBASSEMBLY[self.type]["color"],
    )
    patches = []
    if self.building_blocks:
        for bb in self.building_blocks:
            if bb.marker:
                marker = bb.marker
                patches.append(
                    mpatches.Circle(
                        (
                            float(marker["x"]),
                            float(marker["z"]) + float(self.position.z),
                        ),
                        float(marker["radius"]),
                        facecolor="black",
                        alpha=0.1,
                        edgecolor="black",
                    )
                )
            if bb.line:
                line = bb.line
                xs = cast(list[float], line["x"])
                zs = cast(list[float], line["z"])
                plt.plot(
                    [float(x) + float(x_offset) for x in xs],
                    [float(z) + float(self.position.z) for z in zs],
                    color=line["color"],
                )
    for p in patches:
        plt.gca().add_patch(p)
        plt.ylabel("Height , mm")
        plt.axis("equal")
        plt.grid(which="both", linestyle=":")
plotly
plotly(
    x_offset=DEFAULT_NP_FLOAT64_VALUE,
    y_offset=DEFAULT_NP_FLOAT64_VALUE,
)

Plot the subassembly.

Parameters:

Name Type Description Default
x_offset float64

Offset in the x direction.

DEFAULT_NP_FLOAT64_VALUE
y_offset float64

Offset in the y direction.

DEFAULT_NP_FLOAT64_VALUE

Returns:

Type Description
tuple

Plotly data and layout.

Source code in src/owi/metadatabase/geometry/structures.py
def plotly(
    self,
    x_offset: np.float64 = DEFAULT_NP_FLOAT64_VALUE,
    y_offset: np.float64 = DEFAULT_NP_FLOAT64_VALUE,
):
    """
    Plot the subassembly.

    Parameters
    ----------
    x_offset : np.float64, optional
        Offset in the x direction.
    y_offset : np.float64, optional
        Offset in the y direction.

    Returns
    -------
    tuple
        Plotly data and layout.
    """
    x0, z = self.outline
    data = [
        go.Scattergl(
            x=[x + x_offset for x in x0],
            y=z,
            mode="lines",
            name=self.title,
            line={"color": self.color, "width": 1},
        )
    ]
    layout = go.Layout(
        scene={"aspectmode": "data"},
        yaxis={
            "title": go.layout.yaxis.Title(text="Height , mm"),
            "scaleanchor": "x",
            "scaleratio": 1,
            "type": "linear",
        },
    )
    markers: list[go.Scattergl] = []
    if self.bb:
        for bb in self.bb:
            if bb.marker:
                marker = bb.marker
                markers.append(
                    go.Scattergl(
                        x=[x_offset + float(marker["x"])],
                        y=[float(marker["z"]) + self.position.z],
                        mode="markers",
                        marker={
                            "size": [np.float64(round(float(marker["radius"]) ** (1 / 3)))],
                            "color": "grey",
                        },
                        hovertext=marker["hovertext"],
                        hoverinfo="text",
                        name=bb.title,
                    )
                )
    data.extend(markers)
    return data, layout
as_df
as_df(include_absolute_postion=False)

Transform data into pandas dataframe.

Parameters:

Name Type Description Default
include_absolute_postion bool

Include absolute position of the building blocks, default is False.

False

Returns:

Type Description
DataFrame

Pandas dataframe with the building block information.

Source code in src/owi/metadatabase/geometry/structures.py
def as_df(self, include_absolute_postion: bool = False) -> pd.DataFrame:
    """
    Transform data into pandas dataframe.

    Parameters
    ----------
    include_absolute_postion : bool, optional
        Include absolute position of the building blocks, default
        is False.

    Returns
    -------
    pd.DataFrame
        Pandas dataframe with the building block information.
    """
    out = []
    if self.building_blocks:
        for bb in self.building_blocks:
            out.append(bb.as_dict())
    df = pd.DataFrame(out)
    df = df.set_index("title")
    df = df.sort_values("z", ascending=False)
    cols_at_end = ["description"]
    df = df[[c for c in df if c not in cols_at_end] + [c for c in cols_at_end if c in df]]
    if include_absolute_postion:
        df["absolute_position, m"] = (df["z"] + self.position.z) / 1000
    return df
__str__
__str__()

Return a string representation of the subassembly.

Source code in src/owi/metadatabase/geometry/structures.py
def __str__(self) -> str:
    """Return a string representation of the subassembly."""
    """
    Examples
    --------
    >>> materials = pd.DataFrame([
    ...     {
    ...         "title": "Steel",
    ...         "slug": "steel",
    ...         "id": np.int64(1),
    ...         "description": "",
    ...         "young_modulus": np.float64(210000.0),
    ...         "density": np.float64(7850.0),
    ...         "poisson_ratio": np.float64(0.3),
    ...     }
    ... ])
    >>> data = {
    ...     "id": np.int64(1),
    ...     "title": "SA_1",
    ...     "description": "",
    ...     "slug": "sa",
    ...     "x_position": np.float64(0),
    ...     "y_position": np.float64(0),
    ...     "z_position": np.float64(0),
    ...     "vertical_position_reference_system": "LAT",
    ...     "subassembly_type": "TW",
    ...     "source": "api",
    ...     "asset": np.int64(1),
    ...     "model_definition": np.int64(1),
    ... }
    >>> str(SubAssembly(materials, data))
    'SA_1 subassembly'
    """
    s = str(self.title) + " subassembly"
    return s

Functions