Skip to content

Commit 25c87cb

Browse files
authored
Merge pull request #23 from JuliaAstro/algtutorial
Add a new algorithm guide
2 parents ebf3b44 + 4552bf5 commit 25c87cb

File tree

10 files changed

+330
-9
lines changed

10 files changed

+330
-9
lines changed

.codecov.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
coverage:
22
ignore:
3-
- "examples/"
3+
- "guides/"
44
- "ext/"

docs/make.jl

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,12 @@ makedocs(;
3434
plugins = [bib],
3535
pages = [
3636
"index.md",
37-
"Examples" => [
38-
"examples/getting-started.md",
39-
"examples/plotting.md",
40-
"examples/parallel.md",
41-
"examples/modelingtoolkit.md",
37+
"Guides" => [
38+
"guides/getting-started.md",
39+
"guides/plotting.md",
40+
"guides/parallel.md",
41+
"guides/modelingtoolkit.md",
42+
"guides/new-algorithm.md",
4243
],
4344
"reference.md",
4445
"positioning.md",

docs/src/contributing.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,9 @@ the solution to the issue is clear, you can immediately create a pull request (s
2424

2525
!!! tip
2626
Feel free to ping us after a few days if there are no responses.
27+
28+
## Adding a new algorithm
29+
30+
If you want to contribute a new solar positioning or refraction algorithm, see the
31+
[Adding a New Solar Position Algorithm](@ref new-algorithm) tutorial for a step-by-step
32+
guide.
File renamed without changes.
File renamed without changes.

docs/src/guides/new-algorithm.md

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
# [Adding a New Solar Position Algorithm](@id new-algorithm)
2+
3+
This tutorial walks you through the process of adding a new solar positioning algorithm
4+
to `SolarPosition.jl`. We'll implement a simplified algorithm step by step, covering
5+
all the necessary components: the algorithm struct, core computation, refraction handling,
6+
exports, and tests.
7+
8+
## Overview
9+
10+
Adding a new algorithm involves these steps:
11+
12+
1. [**Create the algorithm struct**](@ref step-1-create-struct) - Define a type that subtypes [`SolarAlgorithm`](@ref SolarPosition.Positioning.SolarAlgorithm).
13+
2. [**Implement the core function**](@ref step-2-implement-core) - Write `_solar_position` for your algorithm.
14+
3. [**Handle refraction**](@ref step-3-handle-refraction) - Define how your algorithm interacts with `DefaultRefraction`.
15+
4. [**Export the algorithm**](@ref step-4-export) - Make it available to users.
16+
5. [**Write tests**](@ref step-5-write-tests) - Validate correctness against reference values.
17+
6. [**Document**](@ref step-6-document) - Add docstrings and update documentation.
18+
7. [**Run pre-commit checks**](@ref step-7-precommit) - Ensure code quality and formatting.
19+
20+
!!! info "Underscore"
21+
Note the underscore prefix in `_solar_position`. This function is internal
22+
and should not be called directly by users. Instead, they will use the public
23+
[`solar_position`](@ref SolarPosition.Positioning.solar_position) function, which dispatches to your implementation based on
24+
the algorithm type struct.
25+
26+
## [Step 1: Create the Algorithm Struct](@id step-1-create-struct)
27+
28+
Create a new file in `src/Positioning/` for your algorithm. For this example, we'll
29+
create a simplified algorithm called `SimpleAlgorithm`.
30+
31+
The struct must:
32+
33+
- Subtype [`SolarAlgorithm`](@ref SolarPosition.Positioning.SolarAlgorithm)
34+
- Include a docstring with `TYPEDEF` and `TYPEDFIELDS` macros
35+
- Document accuracy and provide literature references
36+
37+
```julia
38+
# src/Positioning/simple.jl
39+
40+
"""
41+
\$(TYPEDEF)
42+
43+
Simple solar position algorithm for demonstration purposes.
44+
45+
This algorithm uses basic spherical trigonometry to compute solar positions.
46+
It is provided as a teaching example and is NOT suitable for production use.
47+
48+
# Accuracy
49+
This is a simplified algorithm with limited accuracy (±1°).
50+
51+
# Literature
52+
Based on basic solar geometry principles.
53+
54+
# Fields
55+
\$(TYPEDFIELDS)
56+
"""
57+
struct SimpleAlgorithm <: SolarAlgorithm
58+
"Optional configuration parameter"
59+
param::Float64
60+
end
61+
62+
# Provide a default constructor
63+
SimpleAlgorithm() = SimpleAlgorithm(1.0)
64+
```
65+
66+
## [Step 2: Implement the Core Function](@id step-2-implement-core)
67+
68+
The core of any algorithm is the `_solar_position` function. This function:
69+
70+
- Takes an [`Observer`](@ref SolarPosition.Positioning.Observer), `DateTime`, and your algorithm type
71+
- Returns a [`SolPos{T}`](@ref SolarPosition.Positioning.SolPos) with azimuth, elevation, and zenith angles
72+
- Should be type-stable and performant
73+
74+
Here's the basic structure:
75+
76+
```julia
77+
function _solar_position(obs::Observer{T}, dt::DateTime, alg::SimpleAlgorithm) where {T}
78+
# 1. Convert datetime to Julian date
79+
jd = datetime2julian(dt)
80+
81+
# 2. Calculate days since J2000.0 epoch
82+
n = jd - 2451545.0
83+
84+
# 3. Compute solar coordinates (declination, hour angle, etc.)
85+
# ... your algorithm's calculations here ...
86+
87+
# 4. Calculate local horizontal coordinates
88+
# ... azimuth and elevation calculations ...
89+
90+
# 5. Return the result
91+
return SolPos{T}(azimuth_deg, elevation_deg, zenith_deg)
92+
end
93+
```
94+
95+
### Key Implementation Notes
96+
97+
1. **Use helper functions** from `utils.jl`:
98+
- `fractional_hour(dt)` - Convert time to decimal hours
99+
- `deg2rad(x)` / `rad2deg(x)` - Angle conversions
100+
101+
2. **Observer properties** are pre-computed for efficiency:
102+
- `obs.latitude`, `obs.longitude`, `obs.altitude` - Input values
103+
- `obs.latitude_rad`, `obs.longitude_rad` - Radians versions
104+
- `obs.sin_lat`, `obs.cos_lat` - Precomputed trigonometric values
105+
106+
3. **Type parameter `T`** ensures numerical precision is preserved from the `Observer`
107+
108+
4. **Angle conventions**:
109+
- Azimuth: 0° = North, positive clockwise, range [0°, 360°]
110+
- Elevation: angle above horizon, range [-90°, 90°]
111+
- Zenith: 90° - elevation, range [0°, 180°]
112+
113+
## [Step 3: Handle Default Refraction](@id step-3-handle-refraction)
114+
115+
Each algorithm must specify how it handles `DefaultRefraction`. There are two common
116+
patterns:
117+
118+
### Pattern A: No Refraction by Default (like [`PSA`](@ref SolarPosition.Positioning.PSA))
119+
120+
If your algorithm should NOT apply refraction by default:
121+
122+
```julia
123+
function _solar_position(obs, dt, alg::SimpleAlgorithm, ::DefaultRefraction)
124+
return _solar_position(obs, dt, alg, NoRefraction())
125+
end
126+
127+
# Return type for DefaultRefraction
128+
result_type(::Type{SimpleAlgorithm}, ::Type{DefaultRefraction}, ::Type{T}) where {T} =
129+
SolPos{T}
130+
```
131+
132+
### Pattern B: Apply Refraction by Default (like [`NOAA`](@ref SolarPosition.Positioning.NOAA))
133+
134+
If your algorithm should apply a specific refraction model by default:
135+
136+
```julia
137+
using ..Refraction: HUGHES, DefaultRefraction
138+
139+
function _solar_position(obs, dt, alg::SimpleAlgorithm, ::DefaultRefraction)
140+
return _solar_position(obs, dt, alg, HUGHES())
141+
end
142+
143+
# Return type for DefaultRefraction
144+
result_type(::Type{SimpleAlgorithm}, ::Type{DefaultRefraction}, ::Type{T}) where {T} =
145+
ApparentSolPos{T}
146+
```
147+
148+
The `result_type` function tells the system what return type to expect, enabling
149+
type-stable code for vectorized operations.
150+
151+
## [Step 4: Export the Algorithm](@id step-4-export)
152+
153+
After implementing your algorithm, you need to export it so users can access it.
154+
155+
### 4.1 Include in Positioning Module
156+
157+
Edit `src/Positioning/Positioning.jl` to include your new file:
158+
159+
```julia
160+
# Near the bottom of the file, with other includes
161+
include("utils.jl")
162+
include("deltat.jl")
163+
include("psa.jl")
164+
include("noaa.jl")
165+
include("walraven.jl")
166+
include("usno.jl")
167+
include("spa.jl")
168+
include("simple.jl") # Add your new file
169+
170+
# Add to the export list
171+
export Observer,
172+
PSA,
173+
NOAA,
174+
Walraven,
175+
USNO,
176+
SPA,
177+
SimpleAlgorithm, # Add your algorithm
178+
solar_position,
179+
solar_position!,
180+
SolPos,
181+
ApparentSolPos,
182+
SPASolPos
183+
```
184+
185+
### 4.2 Export from Main Module
186+
187+
Edit `src/SolarPosition.jl` to re-export your algorithm:
188+
189+
```julia
190+
using .Positioning:
191+
Observer, PSA, NOAA, Walraven, USNO, SPA, SimpleAlgorithm, solar_position, solar_position!
192+
193+
# ... later in exports ...
194+
export PSA, NOAA, Walraven, USNO, SPA, SimpleAlgorithm
195+
```
196+
197+
## [Step 5: Write Tests](@id step-5-write-tests)
198+
199+
Create a test file following the naming convention `test/test-simple.jl`.
200+
201+
!!! warning "Generating Validation Data"
202+
It is required to validate your algorithm against known reference values. You
203+
can use a reference implementation of your algorithm (if available) or compare against
204+
trusted solar position calculators. Store these reference values in your test file
205+
and use `@test` statements to ensure your implementation matches them. See the
206+
existing test files like `test/test-psa.jl` for examples of how to structure these tests.
207+
208+
### Running Tests
209+
210+
Tests are automatically discovered by `runtests.jl`. Run them with:
211+
212+
```bash
213+
julia --project=. -e 'using Pkg; Pkg.test()'
214+
```
215+
216+
Or from the Julia REPL:
217+
218+
```julia
219+
using Pkg
220+
Pkg.activate(".")
221+
Pkg.test()
222+
```
223+
224+
## [Step 6: Document Your Algorithm](@id step-6-document)
225+
226+
### Add to Documentation Pages
227+
228+
Update `docs/src/positioning.md` to include your algorithm in the algorithm reference
229+
section.
230+
231+
### Add Literature References
232+
233+
If your algorithm is based on published work, add the reference to `docs/src/refs.bib`:
234+
235+
```bibtex
236+
@article{YourReference,
237+
author = {Author Name},
238+
title = {Algorithm Title},
239+
journal = {Journal Name},
240+
year = {2024},
241+
volume = {1},
242+
pages = {1-10}
243+
}
244+
```
245+
246+
Then cite it in your docstring using `[YourReference](@cite)`.
247+
248+
## [Step 7: Run Pre-commit Checks (Recommended)](@id step-7-precommit)
249+
250+
Before submitting a pull request, it's recommended to run pre-commit hooks locally
251+
to catch formatting and linting issues early. This saves time during code review
252+
and ensures your code meets the project's quality standards. The pre-commit configuration
253+
is defined in the `.pre-commit-config.yaml` file at the root of the repository.
254+
255+
!!! info "CI Runs Pre-commit"
256+
Even if you skip this step locally, GitHub CI will automatically run pre-commit
257+
checks on your pull request. However, running them locally first helps you catch
258+
and fix issues before pushing.
259+
260+
### Installing Pre-commit
261+
262+
```bash
263+
# Install pre-commit (requires Python)
264+
pip install pre-commit
265+
266+
# Install the git hooks (run once per clone)
267+
pre-commit install
268+
```
269+
270+
### Running Pre-commit
271+
272+
```bash
273+
# Run all hooks on all files
274+
pre-commit run --all-files
275+
276+
# Or run on staged files only
277+
pre-commit run
278+
```
279+
280+
Pre-commit runs several checks including:
281+
282+
- **JuliaFormatter** - Ensures consistent code formatting
283+
- **ExplicitImports** - Checks for explicit imports
284+
- **markdownlint** - Lints markdown files
285+
- **typos** - Catches common spelling mistakes
286+
287+
If any checks fail, fix the issues and run pre-commit again until all checks pass.
288+
289+
## Checklist
290+
291+
Before submitting your algorithm for review, ensure you've completed the following:
292+
293+
| Task | Description |
294+
| ---- | ----------- |
295+
| Algorithm struct | Subtypes [`SolarAlgorithm`](@ref SolarPosition.Positioning.SolarAlgorithm) |
296+
| Docstring | Includes `TYPEDEF`, `TYPEDFIELDS`, accuracy, and references |
297+
| `_solar_position` | Function implemented with correct signature |
298+
| Default refraction | Handling defined for [`DefaultRefraction`](@ref SolarPosition.Refraction.DefaultRefraction) |
299+
| `result_type` | Function defined for [`DefaultRefraction`](@ref SolarPosition.Refraction.DefaultRefraction) |
300+
| Export | Algorithm exported from both modules |
301+
| Tests | Cover basic functionality, refraction, vectors, and edge cases |
302+
| Test coverage | Ensure tests cover all new code paths |
303+
| Pre-commit | Checks pass (recommended locally, required in CI) |
304+
| Documentation | Add your algorithm to the list of available algorithms and update the tables in `positioning.md`, `README.md` and `refraction.md` if needed |
305+
| Literature | References added to `refs.bib` and cited in docstrings |
306+
307+
## Additional Resources
308+
309+
- See existing implementations in `src/Positioning/` for reference:
310+
- `psa.jl` - Simple algorithm with no default refraction ([`PSA`](@ref SolarPosition.Positioning.PSA))
311+
- `noaa.jl` - Algorithm with default [`HUGHES`](@ref SolarPosition.Refraction.HUGHES) refraction ([`NOAA`](@ref SolarPosition.Positioning.NOAA))
312+
- `spa.jl` - Complex algorithm with additional output fields ([`SPA`](@ref SolarPosition.Positioning.SPA))
313+
- Check the [Contributing Guidelines](@ref contributing) for general contribution workflow
314+
- Review the [Solar Positioning Algorithms](@ref solar-positioning-algorithms) page for context
File renamed without changes.
File renamed without changes.

docs/src/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ azimuth angles, which are essential for various applications where the sun is im
2828

2929
SolarPosition.jl provides package extensions for advanced use cases:
3030

31-
- **ModelingToolkit Extension**: Integrate solar position calculations into symbolic modeling workflows. Create composable solar energy system models with ModelingToolkit.jl. See the [ModelingToolkit Extension](examples/modelingtoolkit.md) guide for details.
31+
- **ModelingToolkit Extension**: Integrate solar position calculations into symbolic modeling workflows. Create composable solar energy system models with ModelingToolkit.jl. See the [ModelingToolkit Extension](guides/modelingtoolkit.md) guide for details.
3232

3333
- **Makie Extension**: Plotting recipes for solar position visualization.
3434

examples/benchmark_threading.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Benchmark multithreaded solar position calculations
2-
# Run with: julia --threads=auto --project=examples examples/benchmark_threading.jl
2+
# Run with: julia --threads=auto --project=guides guides/benchmark_threading.jl
33

44
using OhMyThreads
55
using SolarPosition
@@ -63,7 +63,7 @@ println("\n🔹 SPA algorithm - Parallel (DynamicScheduler)")
6363

6464
## Output for times = collect(DateTime(2024, 1, 1):Second(1):DateTime(2024, 12, 31, 23))
6565

66-
# julia> include("examples/benchmark_threading.jl")
66+
# julia> include("guides/benchmark_threading.jl")
6767
# Benchmarking solar_position with 32 threads
6868
# Number of timestamps: 31618801
6969
# ======================================================================

0 commit comments

Comments
 (0)