Source code for optiwindnet.db.model
# SPDX-License-Identifier: MIT
# https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/
"""Database model v3 for storage of locations and route sets (Peewee).
Tables:
- NodeSet: location definition
- RouteSet: routeset (i.e. a record of G)
- Method: info on algorithm & options to produce routesets
- Machine: info on machine that generated a routeset
"""
import os
from contextlib import contextmanager
from peewee import (
AutoField,
BlobField,
CharField,
DatabaseProxy,
DateTimeField,
FloatField,
ForeignKeyField,
IntegerField,
Model,
SqliteDatabase,
)
from playhouse.sqlite_ext import JSONField
from ._core import _naive_utc_now
__all__ = ()
database_proxy = DatabaseProxy()
class BaseModel(Model):
class Meta:
database = database_proxy
[docs]
class NodeSet(BaseModel):
digest = BlobField(primary_key=True)
"""SHA256 hash of `VertexC` coordinates array in `.npy` format."""
name = CharField(unique=True)
"""Unique name of the wind farm location."""
T = IntegerField()
"""Number of non-root nodes (wind turbines)."""
R = IntegerField()
"""Number of root nodes (substations)."""
B = IntegerField()
"""Number of border-only vertices."""
VertexC = BlobField()
"""2D coordinates array in UTM, shape (T + B + R, 2)."""
constraint_groups = JSONField()
"""Sizes of each polygon group (first boundary CCW, then obstacles CW)."""
constraint_vertices = JSONField()
"""Concatenated indices into VertexC for constraint polygons."""
landscape_angle = FloatField(null=True)
"""Rotation angle of the landscape layout in radians."""
[docs]
class Method(BaseModel):
digest = BlobField(primary_key=True)
"""SHA256 hash of funhash and JSON-serialized options."""
solver_name = CharField()
"""Name of the solver/algorithm family."""
funname = CharField()
"""Name of the optimization function."""
options = JSONField()
"""Dictionary of function parameters."""
timestamp = DateTimeField(default=_naive_utc_now)
"""UTC timestamp of creation."""
funfile = CharField()
"""Source filename containing the optimization function."""
funhash = BlobField()
"""SHA256 hash of the function bytecode."""
[docs]
class Machine(BaseModel):
id = AutoField()
"""Primary key."""
name = CharField(unique=True)
"""Unique hostname/FQDN of the generating machine."""
attrs = JSONField(null=True)
"""System attributes and specifications dictionary."""
[docs]
class RouteSet(BaseModel):
id = AutoField()
"""Primary key."""
handle = CharField()
"""Standardized wind farm location identifier."""
T = IntegerField()
"""Number of non-root nodes (turbines)."""
R = IntegerField()
"""Number of root nodes (substations)."""
capacity = IntegerField()
"""Cable capacity (maximum turbine load)."""
length = FloatField()
"""Total cable network length."""
runtime = FloatField(null=True)
"""Optimization runtime in seconds."""
feeders_per_root = JSONField()
"""List specifying number of feeders per root substation."""
C = IntegerField(default=0)
"""Number of contour nodes."""
D = IntegerField(default=0)
"""Number of detour nodes."""
creator = CharField(null=True)
"""Short identifier of the routeset origin."""
detextra = FloatField(null=True)
"""Relative length increase due to detour: length = (1+detextra)*undetoured."""
num_diagonals = IntegerField(null=True)
"""Number of diagonal crossing structures."""
tentative = JSONField(null=True)
"""List of vertex pairs representing tentative edges."""
rogue = JSONField(null=True)
"""List of vertex pairs representing rogue/invalid edges."""
timestamp = DateTimeField(null=True, default=_naive_utc_now)
"""UTC timestamp of creation."""
misc = JSONField(default=dict)
"""Dictionary of extra metadata and solver metrics."""
clone2prime = JSONField(null=True)
"""List of length C + D mapping cloned nodes back to original nodes."""
edges = JSONField()
"""Terse tree encoding where edges[i] is the target of node i."""
nodes = ForeignKeyField(NodeSet, backref='routesets')
"""Foreign key link to the layout NodeSet."""
method = ForeignKeyField(Method, backref='routesets')
"""Foreign key link to the Method used."""
machine = ForeignKeyField(Machine, backref='routesets', null=True)
"""Foreign key link to the generating Machine."""
_ALL_MODELS = [NodeSet, Method, Machine, RouteSet]
_DEFAULT_SQLITE_TIMEOUT = 15
[docs]
def open_database(
filepath: str, create_db: bool = False, timeout: int = _DEFAULT_SQLITE_TIMEOUT
) -> SqliteDatabase:
"""Opens the sqlite database v3 file specified in ``filepath``.
Args:
filepath: path to database file
create_db: True → create a new file if it does not exist
timeout: seconds to wait for a locked database to be released
Returns:
SqliteDatabase object (Peewee)
"""
filepath = os.path.abspath(os.path.expanduser(str(filepath)))
if not create_db and not os.path.exists(filepath):
raise OSError(f'Database file not found: {filepath}')
db = SqliteDatabase(
filepath,
pragmas={'foreign_keys': 1},
timeout=timeout,
)
database_proxy.initialize(db)
db.connect()
db.create_tables(_ALL_MODELS)
return db
[docs]
@contextmanager
def database_connection(
filepath: str, create_db: bool = False, timeout: int = _DEFAULT_SQLITE_TIMEOUT
):
"""Open the sqlite database for the duration of a context block.
Args:
filepath: path to database file
create_db: True → create a new file if it does not exist
timeout: seconds to wait for a locked database to be released
Yields:
Connected SqliteDatabase object (Peewee)
"""
db = open_database(filepath, create_db=create_db, timeout=timeout)
try:
yield db
finally:
db.close()