Skip to content
32 changes: 32 additions & 0 deletions solarfarmer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,28 +35,44 @@
EnergyCalculationOptions,
HorizonType,
IAMModelTypeForOverride,
IndexedObject3D,
Inverter,
InverterInput,
InverterOverPowerShutdownMode,
InverterType,
Layout,
Location,
MeteoFileFormat,
MiniSimpleTerrainDto,
MissingMetDataMethod,
ModelChainResponse,
ModuleIndexRange,
ModuleString,
MonthlyAlbedo,
MountingType,
MountingTypeSpecification,
OndFileSupplements,
OrderColumnsPvSystFormatTimeSeries,
OrientationType,
PanFileSupplements,
PowerOptimizerOperationType,
PVPlant,
PVSystem,
QuadDouble,
Rack,
Racks,
ShadingObjects,
SimpleTerrain,
TerrainRowDto,
TerrainRowStartEndColumnsDto,
Tracker,
Trackers,
TrackerSystem,
Transformer,
TransformerLossModelTypes,
TransformerSpecification,
ValidationMessage,
Vector3Double,
)
from .weather import (
TSV_COLUMNS,
Expand Down Expand Up @@ -99,33 +115,49 @@
"EnergyCalculationOptions",
"HorizonType",
"IAMModelTypeForOverride",
"IndexedObject3D",
"Inverter",
"InverterInput",
"InverterOverPowerShutdownMode",
"InverterType",
"Layout",
"Location",
"MeteoFileFormat",
"MiniSimpleTerrainDto",
"MissingMetDataMethod",
"ModelChainResponse",
"ModuleIndexRange",
"ModuleString",
"MonthlyAlbedo",
"MountingType",
"MountingTypeSpecification",
"OndFileSupplements",
"OrderColumnsPvSystFormatTimeSeries",
"OrientationType",
"PanFileSupplements",
"PowerOptimizerOperationType",
"PVPlant",
"PVSystem",
"QuadDouble",
"Rack",
"Racks",
"run_energy_calculation",
"service",
"ShadingObjects",
"SimpleTerrain",
"SolarFarmerAPIError",
"TerrainRowDto",
"TerrainRowStartEndColumnsDto",
"terminate_calculation",
"Tracker",
"TrackerSystem",
"Trackers",
"TSV_COLUMNS",
"Transformer",
"TransformerLossModelTypes",
"TransformerSpecification",
"ValidationMessage",
"Vector3Double",
"from_dataframe",
"from_pvlib",
"from_solcast",
Expand Down
32 changes: 32 additions & 0 deletions solarfarmer/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@
MeteoFileFormat,
MissingMetDataMethod,
OrderColumnsPvSystFormatTimeSeries,
PowerOptimizerOperationType,
TransformerLossModelTypes,
)
from .indexed_object3d import IndexedObject3D
from .inverter import Inverter
from .inverter_input import InverterInput
from .layout import Layout
from .location import Location
from .mini_simple_terrain_dto import MiniSimpleTerrainDto
from .model_chain_response import ModelChainResponse
from .module_index_range import ModuleIndexRange
from .module_string import ModuleString
from .monthly_albedo import MonthlyAlbedo
from .mounting_type_specification import MountingTypeSpecification
from .ond_supplements import OndFileSupplements
Expand All @@ -28,9 +34,19 @@
from .pvsystem.plant_defaults import InverterType, MountingType, OrientationType
from .pvsystem.pvsystem import PVSystem
from .pvsystem.validation import ValidationMessage
from .quad_double import QuadDouble
from .rack import Rack
from .racks import Racks
from .shading_objects import ShadingObjects
from .simple_terrain import SimpleTerrain
from .terrain_row_dto import TerrainRowDto
from .terrain_row_start_end_columns_dto import TerrainRowStartEndColumnsDto
from .tracker import Tracker
from .tracker_system import TrackerSystem
from .trackers import Trackers
from .transformer import Transformer
from .transformer_specification import TransformerSpecification
from .vector3double import Vector3Double

__all__ = [
"AuxiliaryLosses",
Expand All @@ -41,12 +57,17 @@
"EnergyCalculationOptions",
"HorizonType",
"IAMModelTypeForOverride",
"IndexedObject3D",
"Inverter",
"InverterInput",
"InverterOverPowerShutdownMode",
"InverterType",
"ModuleIndexRange",
"ModuleString",
"Layout",
"Location",
"MeteoFileFormat",
"MiniSimpleTerrainDto",
"MissingMetDataMethod",
"ModelChainResponse",
"MonthlyAlbedo",
Expand All @@ -56,12 +77,23 @@
"OrderColumnsPvSystFormatTimeSeries",
"OrientationType",
"PanFileSupplements",
"PowerOptimizerOperationType",
"PVPlant",
"PVSystem",
"QuadDouble",
"Rack",
"Racks",
"ShadingObjects",
"SimpleTerrain",
"SolarFarmerBaseModel",
"TerrainRowDto",
"TerrainRowStartEndColumnsDto",
"Tracker",
"TrackerSystem",
"Trackers",
"Transformer",
"TransformerLossModelTypes",
"TransformerSpecification",
"ValidationMessage",
"Vector3Double",
]
15 changes: 15 additions & 0 deletions solarfarmer/models/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ class InverterOverPowerShutdownMode(str, Enum):
USE_VOLTAGE_DERATE_PROFILE = "UseVoltageDerateProfile"


class PowerOptimizerOperationType(str, Enum):
"""Connection configuration for DC power optimizers (3D calculations only).

Defines how many modules share a single power optimizer and how they
are connected at the optimizer input.
"""

ONE_PER_MODULE = "OnePerModule"
"""One optimizer per module."""
ONE_PER_TWO_MODULES_IN_PARALLEL = "OnePerTwoModulesInParallel"
"""One optimizer shared by two modules connected in parallel."""
ONE_PER_TWO_MODULES_IN_SERIES = "OnePerTwoModulesInSeries"
"""One optimizer shared by two modules connected in series."""


class IAMModelTypeForOverride(str, Enum):
"""IAM model types available for overriding the PAN file default."""

Expand Down
36 changes: 36 additions & 0 deletions solarfarmer/models/indexed_object3d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from pydantic import Field

from ._base import SolarFarmerBaseModel
from .vector3double import Vector3Double


class IndexedObject3D(SolarFarmerBaseModel):
"""A 3D object defined by an indexed mesh of vertices and face indices.
Represents a shading obstacle or building in the 3D scene. The mesh is
described by a set of 3D vertices plus face connectivity expressed as
lists of vertex indices — either quadrilateral faces (``quad_indices``)
or triangular faces (``triangle_indices``).
Attributes
----------
is_building : bool
Whether the object should be treated as a building (affects shading
and irradiance modelling assumptions)
name : str
Descriptive name for this object in the 3D scene
quad_indices : list[list[int]]
Face connectivity for quadrilateral faces. Each inner list contains
four vertex indices referencing entries in ``vertices``
triangle_indices : list[list[int]]
Face connectivity for triangular faces. Each inner list contains
three vertex indices referencing entries in ``vertices``
vertices : list[Vector3Double]
3D vertex positions shared by both quad and triangle faces
"""

is_building: bool
name: str
quad_indices: list[list[int]] = Field(default_factory=list)
triangle_indices: list[list[int]] = Field(default_factory=list)
vertices: list[Vector3Double] = Field(default_factory=list)
10 changes: 5 additions & 5 deletions solarfarmer/models/inverter.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from __future__ import annotations

from typing import Any

from pydantic import Field

from ._base import SolarFarmerBaseModel
from .inverter_input import InverterInput
from .layout import Layout


Expand All @@ -22,8 +21,9 @@ class Inverter(SolarFarmerBaseModel):
Number of identical inverters, >= 1
layouts : list[Layout] or None
Layouts connected to this inverter (2D calculations)
inverter_inputs : list[Any] or None
Inverter inputs for 3D calculations
inverter_inputs : list[InverterInput] or None
Inverter MPPT inputs for 3D calculations. Each entry defines the
module strings and DC losses for one input
ac_wiring_ohmic_loss : float
AC wiring ohmic loss as a fraction, range [0, 1]
name : str or None
Expand All @@ -33,6 +33,6 @@ class Inverter(SolarFarmerBaseModel):
inverter_spec_id: str = Field(..., alias="inverterSpecID", min_length=1)
inverter_count: int = Field(..., ge=1)
layouts: list[Layout] | None = None
inverter_inputs: list[Any] | None = None
inverter_inputs: list[InverterInput] | None = None
ac_wiring_ohmic_loss: float = Field(0.0, ge=0, le=1)
name: str | None = None
71 changes: 71 additions & 0 deletions solarfarmer/models/inverter_input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from __future__ import annotations

from pydantic import Field, model_validator

from ._base import SolarFarmerBaseModel
from .enums import PowerOptimizerOperationType
from .module_string import ModuleString


class InverterInput(SolarFarmerBaseModel):
"""An inverter input instance. Used for 3D calculations only.

Defines the set of module strings connected to a single MPPT input of an
inverter, together with the DC wiring losses and optional power optimizer
configuration.

Attributes
----------
module_specification_id : str
Reference to the PV module specification used by all strings on this
input. Must match a key in ``PVPlant.module_specifications`` (or the
stem of a PAN file uploaded alongside the payload)
module_strings : list[ModuleString]
All module strings attached to this inverter input
dc_ohmic_connector_loss : float
DC cabling ohmic loss as a fraction of DC output power at STC, range [0, 1].
module_mismatch_loss : float
Simple fractional loss to account for module mismatch, range [0, 0.1].
A value of ``0.01`` represents a 1 % loss
dc_ohmic_connector_resistance : float or None
DC cabling resistance in ohms (Ω). Alternative to
``dc_ohmic_connector_loss``: when non-null this value is used directly
and ``dc_ohmic_connector_loss`` is ignored
module_quality_factor : float or None
Fractional adjustment for other modelling corrections, range [-0.4, 0.1].
A positive value implies a gain; negative implies a loss
optimizer_specification_id : str or None
Reference to the DC power optimizer specification. Must match a key in
``PVPlant.optimizer_specifications`` when power optimizers are present
optimizers_per_module : PowerOptimizerOperationType or None
Number and connection configuration of power optimizers per module.
Required when ``optimizer_specification_id`` is provided
fixed_voltage_from_inverter : float or None
Fixed DC bus voltage (V) assumed at the inverter output when power
optimizers are in place
"""

module_specification_id: str = Field(..., alias="moduleSpecificationID", min_length=1)
module_strings: list[ModuleString] = Field(default_factory=list)
dc_ohmic_connector_loss: float = Field(0.0, ge=0, le=1)
module_mismatch_loss: float = Field(0.0, ge=0, le=0.1)
dc_ohmic_connector_resistance: float | None = Field(None, ge=0)
module_quality_factor: float | None = Field(None, ge=-0.4, le=0.1)
optimizer_specification_id: str | None = Field(None, alias="optimizerSpecificationID")
optimizers_per_module: PowerOptimizerOperationType | None = None
fixed_voltage_from_inverter: float | None = Field(None, ge=0)

@model_validator(mode="after")
def _check_invariants(self) -> InverterInput:
has_optimizer_id = self.optimizer_specification_id is not None
has_optimizer_type = self.optimizers_per_module is not None
if has_optimizer_id != has_optimizer_type:
raise ValueError(
"optimizer_specification_id and optimizers_per_module must be provided together"
)
if self.dc_ohmic_connector_resistance is not None and self.dc_ohmic_connector_loss != 0.0:
raise ValueError(
"Provide either dc_ohmic_connector_resistance or dc_ohmic_connector_loss, not both. "
"When dc_ohmic_connector_resistance is set, dc_ohmic_connector_loss is ignored."
)
return self
35 changes: 35 additions & 0 deletions solarfarmer/models/mini_simple_terrain_dto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from pydantic import Field

from ._base import SolarFarmerBaseModel
from .terrain_row_dto import TerrainRowDto
from .vector3double import Vector3Double


class MiniSimpleTerrainDto(SolarFarmerBaseModel):
"""A single terrain tile in a simple terrain representation.

Describes one rectangular patch of terrain as a regular grid of
vertices. The grid dimensions are given by ``num_vertices_across``
(columns) and ``num_vertices_down`` (rows); the actual 3D positions
are stored in ``vertices`` in row-major order.

The ``terrain_rows`` list mirrors the row structure and records which
column ranges within each row are active (contain valid data).

Attributes
----------
num_vertices_across : int
Number of vertices along the horizontal (column) axis of the grid
num_vertices_down : int
Number of vertices along the vertical (row) axis of the grid
terrain_rows : list[TerrainRowDto]
Per-row active column ranges, one entry per row of the grid
vertices : list[Vector3Double]
3D vertex positions in row-major order (``num_vertices_down`` ×
``num_vertices_across`` entries)
"""

num_vertices_across: int
num_vertices_down: int
terrain_rows: list[TerrainRowDto] = Field(default_factory=list)
vertices: list[Vector3Double] = Field(default_factory=list)
Loading
Loading