1pub fn singleaxis(
15 solar_zenith: f64,
16 solar_azimuth: f64,
17 axis_tilt: f64,
18 axis_azimuth: f64,
19 max_angle: f64,
20 backtrack: bool,
21 gcr: f64,
22) -> (f64, f64, f64) {
23 if solar_zenith >= 90.0 { return (0.0, axis_azimuth, 90.0); }
24
25 let sz_rad = solar_zenith.to_radians();
26 let sa_rad = solar_azimuth.to_radians();
27 let aa_rad = axis_azimuth.to_radians();
28 let _at_rad = axis_tilt.to_radians(); let rot_angle_rad = (sz_rad.tan() * (sa_rad - aa_rad).sin()).atan();
32 let mut rot_deg = rot_angle_rad.to_degrees();
33
34 if backtrack && gcr > 0.0 {
36 let temp = (rot_angle_rad.cos() * gcr).clamp(-1.0, 1.0);
37 let shade_angle = temp.acos();
38
39 if rot_angle_rad.abs() > shade_angle {
40 let bt_angle_rad = rot_angle_rad.signum() * (rot_angle_rad.abs() - shade_angle);
41 rot_deg -= bt_angle_rad.to_degrees();
42 }
43 }
44
45 rot_deg = rot_deg.clamp(-max_angle, max_angle);
47
48 let surface_tilt = rot_deg.abs();
49 let surface_azimuth = if rot_deg >= 0.0 {
50 (axis_azimuth - 90.0).rem_euclid(360.0)
51 } else {
52 (axis_azimuth + 90.0).rem_euclid(360.0)
53 };
54
55 let cos_aoi = sz_rad.cos() * surface_tilt.to_radians().cos()
56 + sz_rad.sin() * surface_tilt.to_radians().sin() * (sa_rad - surface_azimuth.to_radians()).cos();
57
58 let aoi = cos_aoi.clamp(-1.0, 1.0).acos().to_degrees();
59
60 (surface_tilt, surface_azimuth, aoi)
61}
62
63pub fn calc_axis_tilt(slope_azimuth: f64, slope_tilt: f64, axis_azimuth: f64) -> f64 {
65 let sa_rad = slope_azimuth.to_radians();
66 let st_rad = slope_tilt.to_radians();
67 let aa_rad = axis_azimuth.to_radians();
68
69 let axis_tilt_rad = (st_rad.tan() * (aa_rad - sa_rad).cos()).atan();
70 axis_tilt_rad.to_degrees()
71}
72
73pub fn calc_surface_orientation(tracker_theta: f64, axis_tilt: f64, axis_azimuth: f64) -> (f64, f64) {
88 let tt_rad = tracker_theta.to_radians();
89 let at_rad = axis_tilt.to_radians();
90
91 let surface_tilt_rad = (tt_rad.cos() * at_rad.cos()).clamp(-1.0, 1.0).acos();
93 let surface_tilt = surface_tilt_rad.to_degrees();
94
95 let sin_st = surface_tilt_rad.sin();
97
98 let azimuth_delta = if sin_st.abs() < 1e-10 {
99 90.0
101 } else {
102 let raw = (tt_rad.sin() / sin_st).clamp(-1.0, 1.0).asin().to_degrees();
104
105 if tracker_theta.abs() < 90.0 {
106 raw
107 } else {
108 -raw + tracker_theta.signum() * 180.0
109 }
110 };
111
112 let surface_azimuth = (axis_azimuth + azimuth_delta).rem_euclid(360.0);
113
114 (surface_tilt, surface_azimuth)
115}
116
117pub fn calc_cross_axis_tilt(slope_azimuth: f64, slope_tilt: f64, axis_azimuth: f64, axis_tilt: f64) -> f64 {
119 let sa_rad = slope_azimuth.to_radians();
120 let st_rad = slope_tilt.to_radians();
121 let aa_rad = axis_azimuth.to_radians();
122 let at_rad = axis_tilt.to_radians();
123
124 let cross_axis_tilt_rad = (st_rad.tan() * (aa_rad - sa_rad).sin() * at_rad.cos()).atan();
125 cross_axis_tilt_rad.to_degrees()
126}
127