.. _arrays_and_mounting:
####################################
Arrays and Mounting Configuration
####################################
This page explains how collector arrays and their mounting configurations work in SunPeek,
including fixed-tilt installations and single-axis tracking systems.
Overview
========
In SunPeek, a **collector array** represents a group of solar thermal collectors that share the same
orientation, collector type, and sensor connections. Arrays are the primary unit for Power Check analysis.
The **mounting** configuration defines how collectors are oriented relative to the sun:
- **Fixed mounting** (``MountingFixed``): Collectors have a constant tilt and azimuth angle.
- **Single-axis tracking** (``MountingSingleAxis``): Collectors rotate around one axis to follow the sun.
.. note::
Single-axis tracking support is available in the Python API. WebUI support for configuring
tracking arrays is planned for a future release.
Mounting Types
==============
MountingFixed
-------------
Use ``MountingFixed`` for fixed-tilt installations where collectors maintain a constant orientation.
**Parameters:**
.. list-table:: MountingFixed Parameters
:header-rows: 1
:widths: 20 50 15 15
* - Parameter
- Description
- Unit
- Range
* - ``surface_tilt``
- Tilt angle from horizontal plane
- degree
- 0-90
* - ``surface_azimuth``
- Compass direction the collectors face (North=0, East=90, South=180, West=270)
- degree
- 0-360
**Example:**
.. code-block:: python
from sunpeek.components import Array, MountingFixed
from sunpeek.common.unit_uncertainty import Q
# South-facing collectors tilted at 30 degrees
mounting = MountingFixed(
surface_tilt=Q(30, 'deg'),
surface_azimuth=Q(180, 'deg'), # South
)
array = Array(
name='Fixed array',
plant=plant,
collector=collector,
mounting=mounting,
area_gr=Q(500, 'm**2'),
sensor_map={...},
)
MountingSingleAxis
------------------
Use ``MountingSingleAxis`` for single-axis tracking installations where collectors rotate around
a fixed axis to follow the sun. The surface orientation is calculated automatically based on
sun position using `pvlib `_.
**Configuration Parameters:**
.. list-table:: MountingSingleAxis Parameters
:header-rows: 1
:widths: 20 50 15 15
* - Parameter
- Description
- Unit
- Range
* - ``axis_tilt``
- Tilt angle of the rotation axis from horizontal
- degree
- 0-90
* - ``axis_azimuth``
- Compass direction of the rotation axis (North=0, East=90, South=180, West=270)
- degree
- 0-360
* - ``max_angle``
- Maximum rotation angle from the horizontal position
- degree
- 0-90
**Common Configurations:**
- **Horizontal N-S axis (tracks E-W)**: ``axis_tilt=0``, ``axis_azimuth=180``
- **Horizontal E-W axis (tracks N-S)**: ``axis_tilt=0``, ``axis_azimuth=90``
- **Tilted axis**: Set ``axis_tilt`` to match latitude for optimal year-round performance
**Example:**
.. code-block:: python
from sunpeek.components import Array, MountingSingleAxis
from sunpeek.common.unit_uncertainty import Q
# Horizontal N-S axis tracker (tracks east-to-west)
mounting = MountingSingleAxis(
axis_tilt=Q(0, 'deg'), # Horizontal rotation axis
axis_azimuth=Q(180, 'deg'), # N-S orientation
max_angle=Q(60, 'deg'), # Maximum rotation from horizontal
)
array = Array(
name='Tracking array',
plant=plant,
collector=collector,
mounting=mounting,
area_gr=Q(500, 'm**2'),
sensor_map={...},
)
Virtual Sensors for Tracking Arrays
===================================
When using ``MountingSingleAxis``, SunPeek automatically calculates time-varying surface orientation
based on the sun position and tracker configuration. These values are available as virtual sensors:
.. list-table:: Tracking Virtual Sensors
:header-rows: 1
:widths: 25 55 20
* - Sensor
- Description
- Virtual
* - ``surface_tilt``
- Instantaneous tilt angle of collector surface
- Always calculated
* - ``surface_azimuth``
- Instantaneous azimuth angle of collector surface
- Always calculated
* - ``ideal_rotation_angle``
- Optimal tracker rotation angle based on sun position
- Always calculated
* - ``rotation_angle``
- Actual measured rotation angle (if sensor available)
- Optional (measured)
**Accessing Virtual Sensors:**
.. code-block:: python
# After loading measurement data
use_csv(plant, csv_files=[...])
# Access time-varying surface orientation
tracking_array = plant.arrays[0]
# Get surface tilt as a time series (pint-pandas Series)
surface_tilt = tracking_array.mounting.surface_tilt.data
print(f"Surface tilt range: {surface_tilt.min():.1f} to {surface_tilt.max():.1f}")
# Get ideal rotation angle
ideal_rotation = tracking_array.mounting.ideal_rotation_angle.data
The virtual sensors are calculated using `pvlib.tracking.singleaxis() `_.
Array Properties
================
Arrays provide convenient properties for accessing orientation, regardless of mounting type:
.. code-block:: python
# For fixed mounting, tilt and azim are scalar Quantities
if array.has_orientation():
print(f"Tilt: {array.tilt}")
print(f"Azimuth: {array.azim}")
# For tracking mounting, tilt and azim are Sensor objects
# containing time-series data
if not array.has_orientation():
print("Tracking array - orientation varies with time")
print(f"Tilt data: {array.tilt.data.head()}")
# The orientation property returns tilt/azim in degrees
# Works for both fixed (scalar) and tracking (raises if time-varying)
if array.has_orientation():
orient = array.orientation
print(f"Orientation: tilt={orient['tilt']}°, azim={orient['azim']}°")
Backward Compatibility
======================
For backward compatibility, the legacy ``tilt`` and ``azim`` parameters on ``Array`` still work.
They are automatically converted to ``MountingFixed`` internally:
.. code-block:: python
# Legacy syntax (still works)
array = Array(
name='Array',
plant=plant,
collector=collector,
tilt=Q(30, 'deg'),
azim=Q(180, 'deg'),
area_gr=Q(500, 'm**2'),
sensor_map={...},
)
# Internally creates: MountingFixed(surface_tilt=Q(30,'deg'), surface_azimuth=Q(180,'deg'))
# Recommended new syntax
array = Array(
name='Array',
plant=plant,
collector=collector,
mounting=MountingFixed(
surface_tilt=Q(30, 'deg'),
surface_azimuth=Q(180, 'deg'),
),
area_gr=Q(500, 'm**2'),
sensor_map={...},
)
.. hint::
While the legacy syntax is supported, we recommend using the explicit ``mounting`` parameter
for new code to make the mounting configuration clear and enable future tracking support.
Additional Array Parameters
===========================
Beyond mounting configuration, arrays have additional parameters for shading and layout calculations:
.. list-table:: Array Parameters
:header-rows: 1
:widths: 20 50 15 15
* - Parameter
- Description
- Unit
- Required
* - ``name``
- Descriptive name for the array
- —
- Yes
* - ``collector``
- Collector type used in this array
- —
- Yes
* - ``mounting``
- Mounting configuration (``MountingFixed`` or ``MountingSingleAxis``)
- —
- Yes
* - ``area_gr``
- Total gross collector area
- m²
- Yes
* - ``row_spacing``
- Distance between collector rows (for shading calculations)
- m
- No
* - ``ground_tilt``
- Slope of the ground beneath collectors
- degree
- No
* - ``sensor_map``
- Mapping of sensor slots to sensor objects
- —
- Yes
For details on sensor mapping, see :ref:`sensor_mapping`.
.. seealso::
- :ref:`collectors` — Solar collector types and parameters
- :ref:`fluids` — Heat transfer fluid configuration
- :ref:`shading` — Internal shading algorithms
- :ref:`python_advanced_topics` — Advanced Python API examples including tracking arrays