Skip to content

Commit 0ee305f

Browse files
committed
[v0.0.6] 2025-08-27
🚨++ New Plug-in Integrated ++ 🚨 - Solar Position plug-in integrated with PyHelios ## Bug Fixes - Fixed plugin registry detection for selective builds (`--plugins visualizer`) - Fixed SolarPosition plugin metadata to correctly reflect optional status - Fixed WeberPennTree constructor to properly handle unavailable plugin scenarios - Updated error messages for plugin availability to match build configurations ## Testing - Enhanced cross-platform tests for selective plugin builds - Fixed test failures when building with limited plugin sets - Improved error handling validation in plugin availability tests
1 parent 5f2b6a8 commit 0ee305f

35 files changed

Lines changed: 5336 additions & 81 deletions

.github/workflows/docs.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ jobs:
3131
uses: actions/checkout@v4
3232
with:
3333
submodules: recursive # Include helios-core submodule for doc assets
34+
fetch-depth: 0 # Fetch full history including tags
3435

3536
- name: Set up Python 3.11
3637
uses: actions/setup-python@v4
@@ -56,8 +57,13 @@ jobs:
5657
5758
- name: Sync version to Doxygen config
5859
run: |
59-
# Get version directly from _version.py to avoid importing PyHelios
60-
VERSION=$(python -c "import sys; sys.path.insert(0, 'pyhelios'); from _version import __version__; print(__version__)")
60+
# Get version from git tags (remove 'v' prefix if present)
61+
VERSION=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "unknown")
62+
if [ "$VERSION" = "unknown" ]; then
63+
echo "Warning: Could not determine version from git tags, using fallback"
64+
# Fallback to setuptools-scm if available
65+
VERSION=$(python -c "import setuptools_scm; print(setuptools_scm.get_version())" 2>/dev/null | sed 's/^v//' || echo "dev")
66+
fi
6167
echo "Updating Doxygen PROJECT_NUMBER to: $VERSION"
6268
sed -i "s/^PROJECT_NUMBER.*$/PROJECT_NUMBER = $VERSION/" docs/Doxyfile.python
6369

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ build/
99
*.egg-info/
1010
*.egg
1111

12+
# Auto-generated version file (setuptools-scm)
13+
pyhelios/_version.py
14+
1215
# IDEs and editors
1316
.vscode/
1417
.idea/

build_scripts/build_helios.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1406,12 +1406,13 @@ def get_default_plugins() -> List[str]:
14061406
- weberpenntree: Procedural tree generation
14071407
- radiation: OptiX-accelerated ray tracing (GPU optional)
14081408
- energybalance: GPU-accelerated thermal modeling and energy balance
1409+
- solarposition: Solar position calculations and sun angle modeling
14091410
14101411
Returns:
14111412
List of default plugins
14121413
"""
14131414
# Return the plugins that are actually integrated into PyHelios
1414-
integrated_plugins = ["visualizer", "weberpenntree", "radiation", "energybalance"]
1415+
integrated_plugins = ["visualizer", "weberpenntree", "radiation", "energybalance", "solarposition"]
14151416

14161417
# Filter by platform compatibility
14171418
default_plugins = []

docs/CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
# Changelog
22

3+
# [v0.0.6] 2025-08-27
4+
5+
🚨++ New Plug-in Integrated ++ 🚨
6+
- Solar Position plug-in integrated with PyHelios
7+
8+
## Bug Fixes
9+
- Fixed CMake build system to only compile wrapper sources for selected plugins
10+
- Fixed plugin registry detection for selective builds (`--plugins visualizer`)
11+
- Fixed SolarPosition plugin metadata to correctly reflect optional status
12+
- Fixed WeberPennTree constructor to properly handle unavailable plugin scenarios
13+
- Updated error messages for plugin availability to match build configurations
14+
15+
## Testing
16+
- Enhanced cross-platform tests for selective plugin builds
17+
- Fixed test failures when building with limited plugin sets
18+
- Improved error handling validation in plugin availability tests
19+
320
# [v0.0.5] 2025-08-27
421

522
- Helios native C++ had several bugs that was causing errors in the last version. Merged in patched version of 1.3.46.

docs/Doxyfile.python

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ INPUT = pyhelios \
131131
docs/plugin_integration_guide.md \
132132
docs/plugin_energybalance.md \
133133
docs/plugin_radiation.md \
134+
docs/plugin_solarposition.md \
134135
docs/plugin_visualizer.md \
135136
docs/plugin_weberpenntree.md \
136137
docs/CHANGELOG.md \
@@ -148,6 +149,7 @@ EXCLUDE = pyhelios/plugins/__pycache__ \
148149
pyhelios/wrappers/ULoggerWrapper.py \
149150
pyhelios/wrappers/URadiationModelWrapper.py \
150151
pyhelios/wrappers/UEnergyBalanceWrapper.py \
152+
pyhelios/wrappers/USolarPositionWrapper.py \
151153
pyhelios/wrappers/UVisualizerWrapper.py \
152154
pyhelios/wrappers/UWeberPennTreeWrapper.py \
153155
pyhelios/config \

docs/DoxygenLayout.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
<!-- Currently Implemented Plugins (Alphabetized) -->
2626
<tab type="user" visible="yes" url="@ref EnergyBalanceDoc" title="Energy Balance Model"/>
2727
<tab type="user" visible="yes" url="@ref RadiationDoc" title="Radiation Model"/>
28+
<tab type="user" visible="yes" url="@ref SolarPositionDoc" title="Solar Position"/>
2829
<tab type="user" visible="yes" url="@ref VisualizerDoc" title="Visualizer"/>
2930
<tab type="user" visible="yes" url="@ref WeberPennTreeDoc" title="Weber-Penn Tree"/>
3031
</tab>

docs/plugin_solarposition.md

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
# Solar Position Plugin Documentation {#SolarPositionDoc}
2+
3+
## Overview
4+
5+
The Solar Position plugin provides comprehensive solar position calculations and sun angle modeling for PyHelios. This plugin enables accurate solar geometry computations essential for radiation modeling, energy balance calculations, and photosynthesis simulations in plant canopy models.
6+
7+
Key features include:
8+
- Precise solar position calculations based on date, time, and geographic location
9+
- Sun direction vector computations for ray tracing applications
10+
- Solar flux calculations with atmospheric corrections
11+
- Sunrise and sunset time calculations
12+
- Atmospheric parameter calibration from time series data
13+
14+
## System Requirements
15+
16+
- **Platforms**: Windows, Linux, macOS
17+
- **Dependencies**: None (uses standard mathematical functions)
18+
- **GPU**: Not required
19+
- **Memory**: Minimal memory footprint
20+
21+
## Installation
22+
23+
### Build with Solar Position Plugin
24+
25+
The Solar Position plugin is included in the default PyHelios build. If building explicitly:
26+
27+
```bash
28+
# Using interactive selection
29+
build_scripts/build_helios --interactive
30+
31+
# Explicit selection
32+
build_scripts/build_helios --plugins solarposition
33+
34+
# Check if available
35+
python -c "from pyhelios.plugins.registry import PluginRegistry; print('solarposition available:', PluginRegistry().is_plugin_available('solarposition'))"
36+
```
37+
38+
## Quick Start
39+
40+
```python
41+
from pyhelios import Context, SolarPosition
42+
43+
# Create context and solar position calculator
44+
with Context() as context:
45+
# Set date and time
46+
context.setDate(year=2024, month=6, day=21) # Summer solstice
47+
context.setTime(hour=12, minute=0, second=0) # Noon
48+
49+
# Create solar position calculator with coordinates (NYC)
50+
with SolarPosition(context, utc_offset=-5, latitude=40.7128, longitude=-74.0060) as solar:
51+
# Get sun elevation angle
52+
elevation = solar.getSunElevation()
53+
print(f"Sun elevation: {elevation:.2f} degrees")
54+
55+
# Get sun direction vector
56+
direction = solar.getSunDirectionVector()
57+
print(f"Sun direction: ({direction.x:.3f}, {direction.y:.3f}, {direction.z:.3f})")
58+
59+
# Calculate solar flux (standard atmospheric conditions)
60+
flux = solar.getSolarFlux(pressure_Pa=101325, temperature_K=288.15,
61+
humidity_rel=0.6, turbidity=0.1)
62+
print(f"Solar flux: {flux:.2f} W/m²")
63+
```
64+
65+
## Examples
66+
67+
### Daily Solar Path Analysis
68+
69+
```python
70+
from pyhelios import Context, SolarPosition
71+
import numpy as np
72+
import matplotlib.pyplot as plt
73+
74+
with Context() as context:
75+
context.setDate(year=2024, month=6, day=21) # Summer solstice
76+
77+
# Create solar position with coordinates (Davis, CA)
78+
with SolarPosition(context, utc_offset=-8, latitude=38.5382, longitude=-121.7617) as solar:
79+
# Calculate sun path throughout the day
80+
hours = np.arange(5, 20, 0.5) # 5 AM to 8 PM
81+
elevations = []
82+
azimuths = []
83+
84+
for hour in hours:
85+
hour_int = int(hour)
86+
minute_int = int((hour - hour_int) * 60)
87+
context.setTime(hour=hour_int, minute=minute_int, second=0)
88+
89+
elevation = solar.getSunElevation()
90+
azimuth = solar.getSunAzimuth()
91+
92+
if elevation > 0: # Sun above horizon
93+
elevations.append(elevation)
94+
azimuths.append(azimuth)
95+
96+
# Plot solar path
97+
plt.figure(figsize=(10, 6))
98+
plt.subplot(1, 2, 1)
99+
plt.plot(hours[:len(elevations)], elevations)
100+
plt.xlabel('Hour of Day')
101+
plt.ylabel('Sun Elevation (degrees)')
102+
plt.title('Sun Elevation vs Time')
103+
104+
plt.subplot(1, 2, 2)
105+
plt.plot(azimuths, elevations)
106+
plt.xlabel('Sun Azimuth (degrees)')
107+
plt.ylabel('Sun Elevation (degrees)')
108+
plt.title('Sun Path (Elevation vs Azimuth)')
109+
plt.show()
110+
```
111+
112+
### Solar Flux Analysis
113+
114+
```python
115+
from pyhelios import Context, SolarPosition
116+
117+
def analyze_solar_flux(latitude, longitude, year, month, day):
118+
"""Analyze solar flux variation throughout a day."""
119+
120+
with Context() as context:
121+
context.setDate(year=year, month=month, day=day)
122+
123+
# Create solar position with provided coordinates
124+
with SolarPosition(context, utc_offset=0, latitude=latitude, longitude=longitude) as solar:
125+
# Get sunrise/sunset times
126+
sunrise = solar.getSunriseTime()
127+
sunset = solar.getSunsetTime()
128+
129+
print(f"Sunrise: {sunrise}")
130+
print(f"Sunset: {sunset}")
131+
# Calculate day length in hours
132+
day_length = (sunset.hour * 3600 + sunset.minute * 60 + sunset.second) - (sunrise.hour * 3600 + sunrise.minute * 60 + sunrise.second)
133+
print(f"Day length: {day_length / 3600:.2f} hours")
134+
135+
# Calculate solar flux at key times
136+
key_times = [
137+
(sunrise.hour, sunrise.minute, 'Sunrise'),
138+
(12, 0, 'Noon'),
139+
(sunset.hour, sunset.minute, 'Sunset')
140+
]
141+
142+
for hour, minute, label in key_times:
143+
context.setTime(hour=hour, minute=minute, second=0)
144+
145+
elevation = solar.getSunElevation()
146+
flux = solar.getSolarFlux(pressure_Pa=101325, temperature_K=288.15,
147+
humidity_rel=0.6, turbidity=0.1)
148+
149+
print(f"{label}: Elevation = {elevation:.1f}°, Flux = {flux:.0f} W/m²")
150+
151+
# Example usage
152+
analyze_solar_flux(40.7128, -74.0060, 2024, 6, 21) # NYC summer solstice
153+
analyze_solar_flux(40.7128, -74.0060, 2024, 12, 21) # NYC winter solstice
154+
```
155+
156+
### Error Handling
157+
158+
```python
159+
from pyhelios import Context, SolarPosition, SolarPositionError
160+
161+
with Context() as context:
162+
try:
163+
# This will fail if coordinates not provided
164+
context.setDate(year=2024, month=7, day=15)
165+
context.setTime(hour=12, minute=0, second=0)
166+
with SolarPosition(context) as solar:
167+
elevation = solar.getSunElevation()
168+
169+
except SolarPositionError as e:
170+
print(f"Solar position error: {e}")
171+
# Error messages include specific requirements
172+
173+
# Retry with coordinates
174+
with SolarPosition(context, utc_offset=-8, latitude=40.0, longitude=-120.0) as solar:
175+
elevation = solar.getSunElevation()
176+
print(f"Sun elevation: {elevation:.2f}°")
177+
```
178+
179+
## Algorithm Details
180+
181+
The Solar Position plugin implements standard solar geometry algorithms:
182+
183+
- **Solar declination**: Based on day of year using standard astronomical formulas
184+
- **Hour angle**: Calculated from local solar time and longitude
185+
- **Solar elevation/azimuth**: Standard spherical trigonometry calculations
186+
- **Atmospheric corrections**: Air mass calculations for solar flux attenuation
187+
- **Time calculations**: Sunrise/sunset based on solar elevation = 0° with refraction correction
188+
189+
These implementations follow established solar engineering practices and produce results accurate to within 0.1° for typical applications.

native/include/pyhelios_wrapper_context.h

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,58 @@ PYHELIOS_API void colorPrimitiveByDataPseudocolor(helios::Context* context, unsi
766766
*/
767767
PYHELIOS_API void colorPrimitiveByDataPseudocolorWithRange(helios::Context* context, unsigned int* uuids, size_t num_uuids, const char* primitive_data, const char* colormap, unsigned int ncolors, float data_min, float data_max);
768768

769+
/**
770+
* @brief Set the simulation time using hour and minute
771+
* @param context Pointer to the Context
772+
* @param hour Hour (0-23)
773+
* @param minute Minute (0-59)
774+
*/
775+
PYHELIOS_API void setTime_HourMinute(helios::Context* context, int hour, int minute);
776+
777+
/**
778+
* @brief Set the simulation time using hour, minute, and second
779+
* @param context Pointer to the Context
780+
* @param hour Hour (0-23)
781+
* @param minute Minute (0-59)
782+
* @param second Second (0-59)
783+
*/
784+
PYHELIOS_API void setTime_HourMinuteSecond(helios::Context* context, int hour, int minute, int second);
785+
786+
/**
787+
* @brief Set the simulation date using day, month, and year
788+
* @param context Pointer to the Context
789+
* @param day Day (1-31)
790+
* @param month Month (1-12)
791+
* @param year Year (1900-3000)
792+
*/
793+
PYHELIOS_API void setDate_DayMonthYear(helios::Context* context, int day, int month, int year);
794+
795+
/**
796+
* @brief Set the simulation date using Julian day and year
797+
* @param context Pointer to the Context
798+
* @param julian_day Julian day (1-366)
799+
* @param year Year (1900-3000)
800+
*/
801+
PYHELIOS_API void setDate_JulianDay(helios::Context* context, int julian_day, int year);
802+
803+
/**
804+
* @brief Get the current simulation time
805+
* @param context Pointer to the Context
806+
* @param hour Output parameter for hour
807+
* @param minute Output parameter for minute
808+
* @param second Output parameter for second
809+
*/
810+
PYHELIOS_API void getTime(helios::Context* context, int* hour, int* minute, int* second);
811+
812+
/**
813+
* @brief Get the current simulation date
814+
* @param context Pointer to the Context
815+
* @param day Output parameter for day
816+
* @param month Output parameter for month
817+
* @param year Output parameter for year
818+
*/
819+
PYHELIOS_API void getDate(helios::Context* context, int* day, int* month, int* year);
820+
769821
#ifdef __cplusplus
770822
}
771823
#endif

0 commit comments

Comments
 (0)