cherry_rs/views/components/
mod.rs

1use std::collections::HashSet;
2
3use anyhow::{anyhow, Result};
4use serde::{Deserialize, Serialize};
5
6use crate::{
7    core::{sequential_model::Surface, Float, RefractiveIndex},
8    RefractiveIndexSpec, SequentialModel, SequentialSubModel,
9};
10
11const TOL: Float = 1e-6;
12
13/// A component is a part of an optical system that can interact with light
14/// rays.
15///
16/// Components come in two types: elements, and stops. Elements are the most
17/// basic compound optical component and are represented as a set of surfaces
18/// pairs. Stops are hard stops that block light rays.
19///
20/// To avoid copying data, only indexes are stored from the surface models are
21/// stored.
22#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
23pub enum Component {
24    Element { surf_idxs: (usize, usize) },
25    Stop { stop_idx: usize },
26    UnpairedSurface { surf_idx: usize },
27}
28
29/// Determine the components of an optical system.
30///
31/// Components are the basic building blocks of an optical system. They are
32/// either elements or stops. Elements are pairs of surfaces that interact with
33/// light rays. Stops are hard stops that block light rays.
34///
35/// Components serve to group surfaces together into individual lenses.
36///
37/// # Arguments
38/// * `sequential_model` - The sequential model of the optical system.
39/// * `background` - The refractive index of the background medium.
40pub fn components_view(
41    sequential_model: &SequentialModel,
42    background: RefractiveIndexSpec,
43) -> Result<HashSet<Component>> {
44    let mut components = HashSet::new();
45
46    let surfaces = sequential_model.surfaces();
47    let surface_pairs = surfaces.iter().zip(surfaces.iter().skip(1)).enumerate();
48    let max_idx = surfaces.len() - 1;
49    let mut paired_surfaces = HashSet::new();
50
51    // TODO: This is a temporary solution to get the submodel due to the need for
52    // gaps. Ignore wavelengths and axes, just get any submodel for now
53    let sequential_sub_model = sequential_model
54        .submodels()
55        .values()
56        .next()
57        .ok_or(anyhow!("No submodels found in the sequential model."))?;
58    let gaps = sequential_sub_model.gaps();
59
60    let background_refractive_index = RefractiveIndex::try_from_spec(&background, None)?;
61
62    if max_idx < 2 {
63        // There are no components because only the object and image plane exist.
64        return Ok(components);
65    }
66
67    for (i, surf_pair) in surface_pairs {
68        if i == 0 || i == max_idx {
69            // Don't include the object or image plane surfaces
70            continue;
71        }
72
73        if let Surface::Stop(_) = surf_pair.0 {
74            // Stops are special, so be sure that they're added before anything else.
75            components.insert(Component::Stop { stop_idx: i });
76            continue;
77        }
78
79        if let Surface::Stop(_) = surf_pair.1 {
80            // Ensure that stops following surfaces are NOT added as a component
81            continue;
82        }
83
84        if same_medium(gaps[i].refractive_index, background_refractive_index) {
85            // Don't include surface pairs that go from background to another medium because
86            // these are gaps.
87            continue;
88        }
89
90        if let Surface::Image(_) = surf_pair.1 {
91            // Check whether the next to last surface has already been paired with another.
92            if !paired_surfaces.contains(&i) {
93                components.insert(Component::UnpairedSurface { surf_idx: i });
94                continue;
95            }
96        }
97
98        components.insert(Component::Element {
99            surf_idxs: (i, i + 1),
100        });
101        paired_surfaces.insert(i);
102        paired_surfaces.insert(i + 1);
103    }
104
105    Ok(components)
106}
107
108/// Two different media are considered the same if their refractive indices are
109/// within a small tolerance of each other.
110fn same_medium(eta_1: RefractiveIndex, eta_2: RefractiveIndex) -> bool {
111    (eta_1.n() - eta_2.n()).abs() < TOL && (eta_1.k() - eta_2.k()).abs() < TOL
112}
113
114#[cfg(test)]
115mod tests {
116    use crate::{core::Float, GapSpec, RefractiveIndexSpec, SequentialModel, SurfaceSpec};
117
118    use super::*;
119
120    const AIR: RefractiveIndexSpec = RefractiveIndexSpec {
121        real: crate::RealSpec::Constant(1.0),
122        imag: None,
123    };
124
125    const NBK7: RefractiveIndexSpec = RefractiveIndexSpec {
126        real: crate::RealSpec::Constant(1.515),
127        imag: None,
128    };
129
130    pub fn empty_system() -> SequentialModel {
131        let surf_0 = SurfaceSpec::Object;
132        let gap_0 = GapSpec {
133            thickness: 1.0,
134            refractive_index: RefractiveIndexSpec {
135                real: crate::RealSpec::Constant(1.0),
136                imag: None,
137            },
138        };
139        let surf_1 = SurfaceSpec::Image;
140
141        let surfaces = vec![surf_0, surf_1];
142        let gaps = vec![gap_0];
143        let wavelengths = vec![0.567];
144
145        SequentialModel::new(&gaps, &surfaces, &wavelengths).unwrap()
146    }
147
148    pub fn silly_unpaired_surface() -> SequentialModel {
149        // A silly system for edge case testing only.
150
151        let surf_0 = SurfaceSpec::Object;
152        let gap_0 = GapSpec {
153            thickness: Float::INFINITY,
154            refractive_index: AIR,
155        };
156        let surf_1 = SurfaceSpec::Conic {
157            semi_diameter: 12.5,
158            radius_of_curvature: 25.8,
159            conic_constant: 0.0,
160            surf_type: crate::SurfaceType::Refracting,
161        };
162        let gap_1 = GapSpec {
163            thickness: 5.3,
164            refractive_index: NBK7,
165        };
166        let surf_2 = SurfaceSpec::Conic {
167            semi_diameter: 12.5,
168            radius_of_curvature: Float::INFINITY,
169            conic_constant: 0.0,
170            surf_type: crate::SurfaceType::Refracting,
171        };
172        let gap_2 = GapSpec {
173            thickness: 46.6,
174            refractive_index: AIR,
175        };
176        let surf_3 = SurfaceSpec::Conic {
177            semi_diameter: 12.5,
178            radius_of_curvature: 25.8,
179            conic_constant: 0.0,
180            surf_type: crate::SurfaceType::Refracting,
181        }; // Surface is unpaired
182        let gap_3 = GapSpec {
183            thickness: 20.0,
184            refractive_index: NBK7,
185        };
186        let surf_4 = SurfaceSpec::Image;
187
188        let surfaces = vec![surf_0, surf_1, surf_2, surf_3, surf_4];
189        let gaps = vec![gap_0, gap_1, gap_2, gap_3];
190        let wavelengths = vec![0.567];
191
192        SequentialModel::new(&gaps, &surfaces, &wavelengths).unwrap()
193    }
194
195    pub fn silly_single_surface_and_stop() -> SequentialModel {
196        // A silly system for edge case testing only.
197
198        let surf_0 = SurfaceSpec::Object;
199        let gap_0 = GapSpec {
200            thickness: Float::INFINITY,
201            refractive_index: AIR,
202        };
203        let surf_1 = SurfaceSpec::Conic {
204            semi_diameter: 12.5,
205            radius_of_curvature: 25.8,
206            conic_constant: 0.0,
207            surf_type: crate::SurfaceType::Refracting,
208        };
209        let gap_1 = GapSpec {
210            thickness: 10.0,
211            refractive_index: NBK7,
212        };
213        let surf_2 = SurfaceSpec::Stop {
214            semi_diameter: 12.5,
215        };
216        let gap_2 = GapSpec {
217            thickness: 10.0,
218            refractive_index: AIR,
219        };
220        let surf_3 = SurfaceSpec::Image;
221
222        let surfaces = vec![surf_0, surf_1, surf_2, surf_3];
223        let gaps = vec![gap_0, gap_1, gap_2];
224        let wavelengths = vec![0.567];
225
226        SequentialModel::new(&gaps, &surfaces, &wavelengths).unwrap()
227    }
228
229    pub fn wollaston_landscape_lens() -> SequentialModel {
230        // Wollaston landscape lens: https://www.youtube.com/watch?v=YN6gTqYVYcw
231        // f/5, EFL = 50 mm
232        // Aperture stop is a hard stop in front of the lens
233
234        let surf_0 = SurfaceSpec::Object;
235        let gap_0 = GapSpec::from_thickness_and_real_refractive_index(Float::INFINITY, 1.0);
236        let surf_1 = SurfaceSpec::Stop { semi_diameter: 5.0 };
237        let gap_1 = GapSpec::from_thickness_and_real_refractive_index(5.0, 1.0);
238        let surf_2 = SurfaceSpec::Conic {
239            semi_diameter: 6.882,
240            radius_of_curvature: Float::INFINITY,
241            conic_constant: 0.0,
242            surf_type: crate::SurfaceType::Refracting,
243        };
244        let gap_2 = GapSpec::from_thickness_and_real_refractive_index(5.0, 1.515);
245        let surf_3 = SurfaceSpec::Conic {
246            semi_diameter: 7.367,
247            radius_of_curvature: -25.84,
248            conic_constant: 0.0,
249            surf_type: crate::SurfaceType::Refracting,
250        };
251        let gap_3 = GapSpec::from_thickness_and_real_refractive_index(47.974, 1.0);
252        let surf_4 = SurfaceSpec::Image;
253
254        let surfaces = vec![surf_0, surf_1, surf_2, surf_3, surf_4];
255        let gaps = vec![gap_0, gap_1, gap_2, gap_3];
256        let wavelengths = vec![0.5876];
257
258        SequentialModel::new(&gaps, &surfaces, &wavelengths).unwrap()
259    }
260
261    // pub fn petzval_lens() -> SequentialModel {
262    //     let surfaces = vec![
263    //         SurfaceSpec::Object,
264    //         SurfaceSpec::Conic {
265    //             semi_diameter: 28.478,
266    //             radius_of_curvature: 99.56266,
267    //             conic_constant: 0.0,
268    //             surf_type: crate::SurfaceType::Refracting,
269    //         },
270    //         SurfaceSpec::Conic {
271    //             semi_diameter: 26.276,
272    //             radius_of_curvature: -86.84002,
273    //             conic_constant: 0.0,
274    //             surf_type: crate::SurfaceType::Refracting,
275    //         },
276    //         SurfaceSpec::Conic {
277    //             semi_diameter: 21.01,
278    //             radius_of_curvature: -1187.63858,
279    //             conic_constant: 0.0,
280    //             surf_type: crate::SurfaceType::Refracting,
281    //         },
282    //         SurfaceSpec::Stop {
283    //             semi_diameter: 33.262,
284    //         },
285    //         SurfaceSpec::Conic {
286    //             semi_diameter: 20.543,
287    //             radius_of_curvature: 57.47491,
288    //             conic_constant: 0.0,
289    //             surf_type: crate::SurfaceType::Refracting,
290    //         },
291    //         SurfaceSpec::Conic {
292    //             semi_diameter: 20.074,
293    //             radius_of_curvature: -54.61685,
294    //             conic_constant: 0.0,
295    //             surf_type: crate::SurfaceType::Refracting,
296    //         },
297    //         SurfaceSpec::Conic {
298    //             semi_diameter: 16.492,
299    //             radius_of_curvature: -614.68633,
300    //             conic_constant: 0.0,
301    //             surf_type: crate::SurfaceType::Refracting,
302    //         },
303    //         SurfaceSpec::Conic {
304    //             semi_diameter: 17.297,
305    //             radius_of_curvature: -38.17110,
306    //             conic_constant: 0.0,
307    //             surf_type: crate::SurfaceType::Refracting,
308    //         },
309    //         SurfaceSpec::Conic {
310    //             semi_diameter: 18.94,
311    //             radius_of_curvature: Float::INFINITY,
312    //             conic_constant: 0.0,
313    //             surf_type: crate::SurfaceType::Refracting,
314    //         },
315    //         SurfaceSpec::Image,
316    //     ];
317    //     let gaps = vec![
318    //         GapSpec::from_thickness_and_real_refractive_index(Float::INFINITY,
319    // 1.0),         GapSpec::from_thickness_and_real_refractive_index(13.0,
320    // 1.5168),         GapSpec::from_thickness_and_real_refractive_index(4.0,
321    // 1.6645),         GapSpec::from_thickness_and_real_refractive_index(40.0,
322    // 1.0),         GapSpec::from_thickness_and_real_refractive_index(40.0,
323    // 1.0),         GapSpec::from_thickness_and_real_refractive_index(12.0,
324    // 1.6074),         GapSpec::from_thickness_and_real_refractive_index(3.0,
325    // 1.6727),         GapSpec::from_thickness_and_real_refractive_index(46.
326    // 82210, 1.0),         GapSpec::from_thickness_and_real_refractive_index(2.
327    // 0, 1.6727),         GapSpec::from_thickness_and_real_refractive_index(1.
328    // 87179, 1.0),     ];
329    //     let wavelengths = vec![0.5876];
330
331    //     SequentialModel::new(&gaps, &surfaces, &wavelengths).unwrap()
332    // }
333
334    #[test]
335    fn test_new_no_components() {
336        let sequential_model = empty_system();
337
338        let components = components_view(&sequential_model, AIR).unwrap();
339
340        assert_eq!(components.len(), 0);
341    }
342
343    #[test]
344    fn test_planoconvex_lens() {
345        let sequential_model = crate::examples::convexplano_lens::sequential_model();
346
347        let components = components_view(&sequential_model, AIR).unwrap();
348
349        assert_eq!(components.len(), 1);
350        assert!(components.contains(&Component::Element { surf_idxs: (1, 2) }));
351    }
352
353    #[test]
354    fn test_silly_single_surface_and_stop() {
355        // This is not a useful system but a good test.
356        let sequential_model = silly_single_surface_and_stop();
357
358        let components = components_view(&sequential_model, AIR).unwrap();
359
360        assert_eq!(components.len(), 1);
361        assert!(components.contains(&Component::Stop { stop_idx: 2 })); // Hard stop
362    }
363
364    #[test]
365    fn test_silly_unpaired_surface() {
366        // This is not a useful system but a good test.
367        let sequential_model = silly_unpaired_surface();
368
369        let components = components_view(&sequential_model, AIR).unwrap();
370
371        assert_eq!(components.len(), 2);
372        assert!(components.contains(&Component::Element { surf_idxs: (1, 2) }));
373        assert!(components.contains(&Component::UnpairedSurface { surf_idx: 3 }));
374    }
375
376    #[test]
377    fn test_wollaston_landscape_lens() {
378        let sequential_model = wollaston_landscape_lens();
379
380        let components = components_view(&sequential_model, AIR).unwrap();
381
382        assert_eq!(components.len(), 2);
383        assert!(components.contains(&Component::Stop { stop_idx: 1 })); // Hard stop
384        assert!(components.contains(&Component::Element { surf_idxs: (2, 3) }));
385        // Lens
386    }
387}