Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions crates/cherry-rs/benches/convexplano_lens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ fn benchmark(c: &mut Criterion) {
let n_air: Rc<dyn RefractiveIndexSpec> = n!(1.0);
let n_nbk7: Rc<dyn RefractiveIndexSpec> = n!(1.515);
let model = sequential_model(n_air, n_nbk7, &WAVELENGTHS);
let paraxial_view = ParaxialView::new(&model, &FIELD_SPECS, false).unwrap();
let field_specs_by_path = [FIELD_SPECS.to_vec()];
let aperture_specs_by_path = [APERTURE_SPEC];
let paraxial_view = ParaxialView::new(&model, &field_specs_by_path, false).unwrap();
let mut group = c.benchmark_group("3D ray trace, convexplano lens");

group.bench_function("ray_trace_3d_view", |b| {
b.iter(|| {
ray_trace_3d_view(
black_box(&APERTURE_SPEC),
black_box(&FIELD_SPECS),
black_box(&aperture_specs_by_path),
black_box(&field_specs_by_path),
black_box(&model),
black_box(&paraxial_view),
black_box(SamplingConfig {
Expand Down
11 changes: 6 additions & 5 deletions crates/cherry-rs/benches/f_theta_scan_lens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,19 @@ const APERTURE_SPEC: ApertureSpec = ApertureSpec::EntrancePupil { semi_diameter:

fn benchmark(c: &mut Criterion) {
let model = sequential_model(n!(1.0), n!(1.84666), &WAVELENGTHS);
let field_specs = vec![FieldSpec::Angle {
let field_specs_by_path = [vec![FieldSpec::Angle {
chi: 20.0,
phi: 90.0,
}];
let paraxial_view = ParaxialView::new(&model, &field_specs, false).unwrap();
}]];
let aperture_specs_by_path = [APERTURE_SPEC];
let paraxial_view = ParaxialView::new(&model, &field_specs_by_path, false).unwrap();
let mut group = c.benchmark_group("3D ray trace, f-theta scan lens");

group.bench_function("ray_trace_3d_view, 20 deg off-axis", |b| {
b.iter(|| {
ray_trace_3d_view(
black_box(&APERTURE_SPEC),
black_box(&field_specs),
black_box(&aperture_specs_by_path),
black_box(&field_specs_by_path),
black_box(&model),
black_box(&paraxial_view),
black_box(SamplingConfig {
Expand Down
17 changes: 7 additions & 10 deletions crates/cherry-rs/src/core/sequential_model/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1080,16 +1080,13 @@ mod tests {
.model;

// Path 1's gap was solved to place its image at the paraxial focus.
let pv = ParaxialView::new(
&model,
&[FieldSpec::Angle {
chi: 0.0,
phi: 90.0,
}],
false,
)
.unwrap();
let sub1 = pv.get_for_path(1, 0, 0).unwrap();
let field_specs = vec![FieldSpec::Angle {
chi: 0.0,
phi: 90.0,
}];
let pv = ParaxialView::new(&model, &[field_specs.clone(), field_specs], false).unwrap();
let tangential_vec_id = pv.tangential_vec_id_for_phi(1, std::f64::consts::FRAC_PI_2);
let sub1 = pv.get_for_path(1, 0, tangential_vec_id).unwrap();
assert_abs_diff_eq!(
sub1.marginal_ray().rays_at_surface(2)[0].height,
0.0,
Expand Down
9 changes: 5 additions & 4 deletions crates/cherry-rs/src/core/sequential_model/solves/fno.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ mod tests {
.build()
.expect("build should succeed")
.model;
let pv = ParaxialView::new(&model, &field_specs(), false).unwrap();
let pv = ParaxialView::new(&model, &[field_specs()], false).unwrap();
(model, pv)
}

Expand Down Expand Up @@ -437,7 +437,7 @@ mod tests {
.expect("build should succeed")
.model;

let pv = ParaxialView::new(&model, &field_specs(), false).unwrap();
let pv = ParaxialView::new(&model, &[field_specs()], false).unwrap();
let sub = pv.get(0, 0).unwrap();

// F/# constraint satisfied.
Expand Down Expand Up @@ -548,10 +548,11 @@ mod tests {
chi: 0.0,
phi: 90.0,
}];
let pv = ParaxialView::new(&model, &field_specs, false).unwrap();
let pv = ParaxialView::new(&model, &[field_specs.clone(), field_specs], false).unwrap();

// Path 1 (the evaluation path): F/# matches the target directly.
let sub1 = pv.get_for_path(1, 0, 0).unwrap();
let tangential_vec_id_1 = pv.tangential_vec_id_for_phi(1, std::f64::consts::FRAC_PI_2);
let sub1 = pv.get_for_path(1, 0, tangential_vec_id_1).unwrap();
assert_abs_diff_eq!(sub1.paraxial_fno().abs(), target, epsilon = 1e-3);

// Path 0 sees the *same physical surface* — same store index, same
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ mod tests {
.expect("build should succeed")
.model;

let pv = ParaxialView::new(&model, &field_specs(), false).unwrap();
let pv = ParaxialView::new(&model, &[field_specs()], false).unwrap();
let sub = pv.get(0, 0).unwrap();

let marginal_at_image = sub.marginal_ray().rays_at_surface(3)[0].height;
Expand All @@ -238,7 +238,7 @@ mod tests {
.expect("build should succeed")
.model;

let pv = ParaxialView::new(&model, &field_specs(), false).unwrap();
let pv = ParaxialView::new(&model, &[field_specs()], false).unwrap();
let sub = pv.get(0, 0).unwrap();

let marginal_at_image = sub.marginal_ray().rays_at_surface(3)[0].height;
Expand Down Expand Up @@ -372,7 +372,7 @@ mod tests {
.expect("build should succeed")
.model;

let pv = ParaxialView::new(&model, &field_specs(), false).unwrap();
let pv = ParaxialView::new(&model, &[field_specs()], false).unwrap();
let sub = pv.get(0, 0).unwrap();
let marginal_at_image = sub.marginal_ray().rays_at_surface(3)[0].height;
assert_abs_diff_eq!(marginal_at_image, 0.0, epsilon = 1e-4);
Expand Down
10 changes: 8 additions & 2 deletions crates/cherry-rs/src/gui/compute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ fn run_compute(
let surfaces = build_surface_descs(&seq);
let fields = build_field_descs(&parsed.fields);

let pv = match ParaxialView::new(&seq, &parsed.fields, false) {
let pv = match ParaxialView::new(&seq, std::slice::from_ref(&parsed.fields), false) {
Ok(p) => p,
Err(e) => {
return ResultPackage {
Expand Down Expand Up @@ -179,7 +179,13 @@ fn run_compute(
n_fan_rays: req.specs.n_fan_rays as usize,
full_pupil_spacing,
};
let trace = match ray_trace_3d_view(&parsed.aperture, &parsed.fields, &seq, &pv, config) {
let trace = match ray_trace_3d_view(
&[parsed.aperture],
std::slice::from_ref(&parsed.fields),
&seq,
&pv,
config,
) {
Ok(t) => Some(t),
Err(e) => {
log::warn!("Ray trace failed: {e}");
Expand Down
8 changes: 5 additions & 3 deletions crates/cherry-rs/src/gui/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,13 +316,15 @@ fn apply_group_transforms(

for group in lens_groups {
// Collect the full surface index list for this group from the component map.
// `nominal` is always single-path (built via from_surface_specs), so there is
// exactly one PathComponent per component and no path filtering is needed.
let mut all_surfs: Vec<usize> = Vec::new();
for &first_surf in &group.component_first_surfs {
if let Some(comp) = components
if let Some(pc) = components
.iter()
.find(|c| component_first_idx(c) == first_surf)
.find(|pc| component_first_idx(&pc.component) == first_surf)
{
match comp {
match &pc.component {
Component::Element { surf_idxs } => all_surfs.extend(surf_idxs),
Component::Iris { stop_idx } => all_surfs.push(*stop_idx),
Component::Mirror { surf_idx } => all_surfs.push(*surf_idx),
Expand Down
35 changes: 24 additions & 11 deletions crates/cherry-rs/src/gui/panels/lens_overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
model::{LensGroupSpec, SystemSpecs},
result_package::ResultPackage,
},
views::components::Component,
views::components::{Component, PathComponent},
};

/// Floating panel that groups auto-detected optical components and exposes
Expand Down Expand Up @@ -58,9 +58,12 @@ fn default_group_name(c: &Component) -> String {
/// discarded groups and `n_new` is the number of newly added groups.
fn validate_and_sync(
lens_groups: &mut Vec<LensGroupSpec>,
components: &[Component],
components: &[PathComponent],
) -> (Vec<String>, usize) {
let known: HashSet<usize> = components.iter().map(component_first_idx).collect();
let known: HashSet<usize> = components
.iter()
.map(|pc| component_first_idx(&pc.component))
.collect();

let mut stale_names = Vec::new();
let valid: Vec<LensGroupSpec> = lens_groups
Expand All @@ -84,10 +87,10 @@ fn validate_and_sync(
.collect();

let mut n_new = 0usize;
for comp in components {
let first = component_first_idx(comp);
for pc in components {
let first = component_first_idx(&pc.component);
if !covered.contains(&first) {
let mut g = LensGroupSpec::new(default_group_name(comp));
let mut g = LensGroupSpec::new(default_group_name(&pc.component));
g.component_first_surfs = vec![first];
lens_groups.push(g);
n_new += 1;
Expand Down Expand Up @@ -174,7 +177,7 @@ impl LensOverlayPanel {
let components = &result.components;
let comp_lookup: std::collections::HashMap<usize, &Component> = components
.iter()
.map(|c| (component_first_idx(c), c))
.map(|pc| (component_first_idx(&pc.component), &pc.component))
.collect();

let n_groups = specs.lens_groups.len();
Expand Down Expand Up @@ -459,6 +462,16 @@ mod tests {
Component::Mirror { surf_idx: idx }
}

fn as_path_components(components: Vec<Component>) -> Vec<PathComponent> {
components
.into_iter()
.map(|component| PathComponent {
path_id: 0,
component,
})
.collect()
}

fn make_group(name: &str, first_surfs: Vec<usize>) -> LensGroupSpec {
let mut g = LensGroupSpec::new(name);
g.component_first_surfs = first_surfs;
Expand All @@ -468,7 +481,7 @@ mod tests {
#[test]
fn validate_adds_default_groups_when_empty() {
let mut groups: Vec<LensGroupSpec> = Vec::new();
let components = vec![make_element(vec![1, 2]), make_mirror(3)];
let components = as_path_components(vec![make_element(vec![1, 2]), make_mirror(3)]);
let (stale, n_new) = validate_and_sync(&mut groups, &components);
assert!(stale.is_empty());
assert_eq!(n_new, 2);
Expand All @@ -480,7 +493,7 @@ mod tests {
#[test]
fn validate_discards_stale_group() {
let mut groups = vec![make_group("OldGroup", vec![5])];
let components = vec![make_element(vec![1, 2])];
let components = as_path_components(vec![make_element(vec![1, 2])]);
let (stale, n_new) = validate_and_sync(&mut groups, &components);
assert_eq!(stale, vec!["OldGroup"]);
assert_eq!(n_new, 1);
Expand All @@ -491,7 +504,7 @@ mod tests {
#[test]
fn validate_keeps_valid_groups_and_adds_new_component() {
let mut groups = vec![make_group("MyLens", vec![1])];
let components = vec![make_element(vec![1, 2]), make_mirror(3)];
let components = as_path_components(vec![make_element(vec![1, 2]), make_mirror(3)]);
let (stale, n_new) = validate_and_sync(&mut groups, &components);
assert!(stale.is_empty());
assert_eq!(n_new, 1);
Expand All @@ -503,7 +516,7 @@ mod tests {
#[test]
fn validate_groups_sorted_by_first_surf() {
let mut groups: Vec<LensGroupSpec> = Vec::new();
let components = vec![make_mirror(5), make_element(vec![1, 2])];
let components = as_path_components(vec![make_mirror(5), make_element(vec![1, 2])]);
let (_, _) = validate_and_sync(&mut groups, &components);
assert_eq!(groups[0].component_first_surfs[0], 1);
assert_eq!(groups[1].component_first_surfs[0], 5);
Expand Down
4 changes: 2 additions & 2 deletions crates/cherry-rs/src/gui/result_package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::collections::HashMap;
use crate::{
CrossSectionView, FieldSpec, ParaxialView, TraceResultsCollection,
core::math::{linalg::mat3x3::Mat3x3, vec3::Vec3},
views::components::Component,
views::components::PathComponent,
};

/// Post-solve parameter values keyed by their index in the surfaces table.
Expand Down Expand Up @@ -47,7 +47,7 @@ pub struct ResultPackage {
pub error: Option<String>,
pub solved_values: SolvedValues,
/// Auto-detected optical components from the sequential model.
pub components: Vec<Component>,
pub components: Vec<PathComponent>,
}

impl ResultPackage {
Expand Down
9 changes: 7 additions & 2 deletions crates/cherry-rs/src/gui/windows/paraxial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ fn render_paraxial_content(ui: &mut egui::Ui, r: &ResultPackage) {
if r.wavelengths.len() > 1 {
let pac = pv.primary_axial_color();
for &v_idx in &v_indices {
if let Some(&color) = pac.get(v_idx) {
if let Some(color) = pac
.iter()
.find(|ac| ac.path_id == 0 && ac.tangential_vec_id == v_idx)
.map(|ac| ac.color)
{
let phi_suffix = if n_v > 1 {
let phi_deg = pv.phi_deg(v_idx);
format!(" (\u{03c6} = {phi_deg:.0}\u{00b0})")
Expand Down Expand Up @@ -291,7 +295,8 @@ mod tests {
None,
)
.expect("model");
let pv = ParaxialView::new(&seq, &parsed.fields, false).expect("paraxial");
let pv =
ParaxialView::new(&seq, std::slice::from_ref(&parsed.fields), false).expect("paraxial");
let wls = seq.wavelengths().to_vec();
ResultPackage {
id: 1,
Expand Down
14 changes: 11 additions & 3 deletions crates/cherry-rs/src/gui/windows/ray_fan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ fn chief_ray_image_pos(
let Some(pv) = &r.paraxial else {
return (None, false);
};
let tangential_vec_id = pv.tangential_vec_id_for_phi(phi);
let tangential_vec_id = pv.tangential_vec_id_for_phi(0, phi);
let Some(sv) = pv.get(wl_id, tangential_vec_id) else {
return (None, false);
};
Expand Down Expand Up @@ -518,12 +518,20 @@ mod tests {
None,
)
.expect("model");
let pv = ParaxialView::new(&seq, &parsed.fields, false).expect("paraxial");
let pv =
ParaxialView::new(&seq, std::slice::from_ref(&parsed.fields), false).expect("paraxial");
let config = SamplingConfig {
n_fan_rays: 11,
full_pupil_spacing: 0.1,
};
let trace = ray_trace_3d_view(&parsed.aperture, &parsed.fields, &seq, &pv, config).ok();
let trace = ray_trace_3d_view(
&[parsed.aperture],
std::slice::from_ref(&parsed.fields),
&seq,
&pv,
config,
)
.ok();
let wls = seq.wavelengths().to_vec();

// Build surface descs manually (mirrors compute.rs logic).
Expand Down
10 changes: 6 additions & 4 deletions crates/cherry-rs/src/gui/windows/spot_diagram.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,8 @@ mod tests {
None,
)
.expect("model");
let pv = ParaxialView::new(&seq, &parsed.fields, false).expect("paraxial");
let pv =
ParaxialView::new(&seq, std::slice::from_ref(&parsed.fields), false).expect("paraxial");

let result = ResultPackage {
id: 1,
Expand Down Expand Up @@ -447,10 +448,11 @@ mod tests {
None,
)
.expect("model");
let pv = ParaxialView::new(&seq, &parsed.fields, false).expect("paraxial");
let pv =
ParaxialView::new(&seq, std::slice::from_ref(&parsed.fields), false).expect("paraxial");
let trace = ray_trace_3d_view(
&parsed.aperture,
&parsed.fields,
&[parsed.aperture],
std::slice::from_ref(&parsed.fields),
&seq,
&pv,
crate::views::ray_trace_3d::SamplingConfig {
Expand Down
Loading
Loading