Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
3ef72ec
plots
MarleneBusch Apr 28, 2026
9f43406
update
MarleneBusch Apr 28, 2026
9bb386d
add parameters
MarleneBusch May 8, 2026
e803f06
remove dtype
MarleneBusch May 8, 2026
68b0404
Merge branch 'main' into features/combined_optimization
MarleneBusch May 8, 2026
f7ee715
Merge branch 'main' into features/combined_optimization
MarleneBusch May 20, 2026
57fbd03
add reduction parameter to loss per heliostat
MarleneBusch May 20, 2026
2e8531f
add optimization into inverse kinematics
MarleneBusch May 20, 2026
ac93316
change setup
MarleneBusch May 20, 2026
fd31f63
fix loss functions
MarleneBusch May 21, 2026
5f526cf
import angle loss
MarleneBusch May 21, 2026
6b8d335
add constant for second kinematic reconstruction method
MarleneBusch May 21, 2026
4d25e28
rename reduce method
MarleneBusch May 21, 2026
fd17db2
add geometry method for reconstruction
MarleneBusch May 21, 2026
9774c61
fix import
MarleneBusch May 21, 2026
5c2f489
fix
MarleneBusch May 21, 2026
a5170ff
fix and replace old inverse kinematics
MarleneBusch May 27, 2026
2df8315
clean up methods
MarleneBusch May 27, 2026
5c9ce2c
add missing param in docstring
MarleneBusch May 27, 2026
d53ac59
add method and dataclass for train test data split
MarleneBusch May 27, 2026
9971691
remove old plot
MarleneBusch May 27, 2026
589c967
remove print
MarleneBusch May 28, 2026
07886ca
improve plot method
MarleneBusch May 28, 2026
b23b3ee
add test train data split
MarleneBusch May 28, 2026
65ffbfe
rename kinematic reconstruction method
MarleneBusch May 28, 2026
8062f5a
formatting
MarleneBusch May 28, 2026
6be7cda
formatting and new test data files
MarleneBusch May 29, 2026
00b188c
add normalization in angle loss
MarleneBusch May 29, 2026
8248fb6
fix early stopping and plot logic
MarleneBusch May 29, 2026
f5ab894
clean up comments
MarleneBusch May 29, 2026
2497fc5
fix final loss accumulation
MarleneBusch May 29, 2026
9f7a3eb
add parameters to kinematic models and fix outputs
MarleneBusch May 29, 2026
ab59fd1
add new test for angle loss
MarleneBusch May 29, 2026
868c08c
fix setups and error tests
MarleneBusch May 29, 2026
fa2fb62
new data
MarleneBusch May 29, 2026
ef864a8
mypy and ruff
MarleneBusch May 29, 2026
37348ff
update data
MarleneBusch May 29, 2026
8023f21
clean up tutorials
MarleneBusch May 29, 2026
57c285b
fix
MarleneBusch May 29, 2026
2dd8497
rename motor position optimizer to aim point optimizer
MarleneBusch May 29, 2026
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
374 changes: 225 additions & 149 deletions artist/field/kinematics_rigid_body.py

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions artist/optim/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .aim_point_optimizer import AimPointOptimizer
from .kinematics_reconstructor import KinematicsReconstructor
from .loss import (
AngleLoss,
Expand All @@ -6,22 +7,22 @@
Loss,
PixelLoss,
VectorLoss,
mean_loss_per_heliostat,
reduce_loss_per_sample,
)
from .motor_position_optimizer import MotorPositionsOptimizer
from .regularizers import IdealSurfaceRegularizer, SmoothnessRegularizer
from .surface_reconstructor import SurfaceReconstructor
from .training import EarlyStopping, cyclic, exponential, reduce_on_plateau

__all__ = [
"KinematicsReconstructor",
"MotorPositionsOptimizer",
"AimPointOptimizer",
"SurfaceReconstructor",
"SmoothnessRegularizer",
"IdealSurfaceRegularizer",
"EarlyStopping",
"Loss",
"VectorLoss",
"reduce_loss_per_sample",
"FocalSpotLoss",
"PixelLoss",
"KLDivergenceLoss",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
from artist.util.env import DdpSetup, get_device

log = logging.getLogger(__name__)
"""A logger for the motor positions optimizer."""
"""A logger for the aim point optimizer."""


class MotorPositionsOptimizer:
class AimPointOptimizer:
"""
An optimizer used to find optimal motor positions for the heliostats.
An optimizer used to find optimal aim points via individual motor positions for the heliostats.

The optimization loss is defined as the loss between the combined predicted and target
flux densities. Additionally, there is one constraint that maximizes the flux integral,
Expand Down Expand Up @@ -74,7 +74,7 @@ def __init__(
device: torch.device | None = None,
) -> None:
"""
Initialize the motor-positions optimizer.
Initialize the aim points optimizer.

Parameters
----------
Expand Down Expand Up @@ -110,7 +110,7 @@ def __init__(
rank = ddp_setup["rank"]

if rank == 0:
log.info("Create a motor positions optimizer.")
log.info("Create an aim points optimizer.")

self.ddp_setup = ddp_setup
self.scenario = scenario
Expand All @@ -130,7 +130,7 @@ def optimize(
device: torch.device | None = None,
) -> tuple[torch.Tensor, dict[str, list], torch.Tensor, torch.Tensor, torch.Tensor]:
r"""
Optimize the motor positions.
Optimize the motor positions for optimal aim points.

The motor positions are optimized through a reparameterization to ensure stable training
across different heliostats with widely varying initial motor positions and ranges. Motor
Expand Down Expand Up @@ -174,7 +174,7 @@ def optimize(
Returns
-------
torch.Tensor
Final loss of the motor position optimization.
Final loss of the aim point optimization.
dict[str, list]
Loss history over epochs, with keys ``"total_loss"``, ``"flux_loss"``,
``"local_flux_constraint"``, ``"intercept_constraint"``, ``"flux_integral_constraint"``,
Expand All @@ -190,7 +190,7 @@ def optimize(
rank = self.ddp_setup["rank"]

if rank == 0:
log.info("Start the motor positions optimization.")
log.info("Start the aim point optimization.")

optimizable_parameters_all_groups = []
scales_all_groups = []
Expand All @@ -210,19 +210,17 @@ def optimize(
]
)

# Per-group pre-alignment + reparameterization prep
# Per-group pre-alignment and parameterization
for group_index, group in enumerate(
self.scenario.heliostat_field.heliostat_groups
):
# All heliostats are initially active.
active_heliostats_masks_all_groups.append(
torch.ones(
group.number_of_heliostats,
dtype=torch.int32,
device=device,
)
)
# All target indices are initially set to selected target area.
target_area_indices_all_groups.append(
torch.full(
(group.number_of_heliostats,),
Expand All @@ -231,20 +229,15 @@ def optimize(
device=device,
)
)
# Repeat the same incident ray direction per heliostat.
incident_ray_directions_all_groups.append(
self.incident_ray_direction.repeat(group.number_of_heliostats, 1)
)

# Align all heliostats once, to the given incident ray direction and target, to set initial motor positions.
# The motor positions are set automatically in the ``align_surfaces_with_incident_ray_directions()`` method.
# Activate heliostats.
group.activate_heliostats(
active_heliostats_mask=active_heliostats_masks_all_groups[group_index],
device=device,
)

# Align heliostats.
group.align_surfaces_with_incident_ray_directions(
aim_points=self.scenario.solar_tower.get_centers_of_target_areas(
target_area_indices=target_area_indices_all_groups[group_index],
Expand All @@ -260,7 +253,6 @@ def optimize(
group.kinematics.active_motor_positions.detach().clone()
)
initial_motor_positions_all_groups.append(initial_motor_positions)
# Read actuator min/max positions.
motor_positions_minimum = (
group.kinematics.actuators.non_optimizable_parameters[
:, indices.actuator_min_motor_position
Expand All @@ -277,7 +269,6 @@ def optimize(
torch.minimum(lower_margin, upper_margin).clamp(min=1.0)
)

# Create trainable unconstrained tensor.
optimizable_parameters_all_groups.append(
torch.nn.Parameter(
torch.zeros_like(initial_motor_positions, device=device)
Expand Down Expand Up @@ -307,7 +298,6 @@ def optimize(
relative=True,
)

# Determine target-plane dimensions.
target_plane_dimensions = torch.empty(2, device=device)
target_areas, index = self.scenario.solar_tower.index_to_target_area[
self.target_area_index
Expand All @@ -317,9 +307,9 @@ def optimize(
< self.scenario.solar_tower.number_of_target_areas_per_type[
indices.planar_target_areas
]
): # Planar target
):
target_plane_dimensions = target_areas.dimensions[self.target_area_index] # type: ignore[attr-defined]
else: # Cylinder target
else:
cylinder_indices = (
self.target_area_index
- self.scenario.solar_tower.number_of_target_areas_per_type[
Expand Down Expand Up @@ -383,7 +373,7 @@ def optimize(
on_target_factors = torch.zeros_like(intercept_factors, device=device)
blocking_factors = torch.zeros_like(intercept_factors, device=device)

# First per-group loop: Reconstruct and apply motor positions.
# First per-group loop: Align all heliostats such that blocking can be computed correctly.
for heliostat_group_index in self.ddp_setup["groups_to_ranks_mapping"][
rank
]:
Expand All @@ -405,7 +395,6 @@ def optimize(
* scales_all_groups[heliostat_group_index]
)

# Activate heliostats.
heliostat_alignment_group.activate_heliostats(
active_heliostats_mask=active_heliostats_masks_all_groups[
heliostat_group_index
Expand All @@ -422,7 +411,7 @@ def optimize(
device=device,
)

# Second per-group loop: Trace rays and accumulate outputs.
# Second per-group loop: Trace rays and accumulate fluxes.
for heliostat_group_index in self.ddp_setup["groups_to_ranks_mapping"][
rank
]:
Expand Down Expand Up @@ -464,15 +453,13 @@ def optimize(
device=device,
)
sample_indices_for_local_rank = ray_tracer.get_sampler_indices()
# Retrieve flux on selected target.
flux_distribution_on_target = ray_tracer.get_bitmaps_per_target(
bitmaps_per_heliostat=flux_distributions,
target_area_indices=target_area_indices_all_groups[
heliostat_group_index
][sample_indices_for_local_rank],
device=device,
)[self.target_area_index]
# Add to global flux.
total_flux = total_flux + flux_distribution_on_target

global_indices = (
Expand All @@ -482,7 +469,6 @@ def optimize(
on_target_factors[global_indices] = on_target_factor
blocking_factors[global_indices] = blocking_factor

# Reduce total flux in the distributed case.
if self.ddp_setup["is_distributed"]:
total_flux = torch.distributed.nn.functional.all_reduce(
total_flux,
Expand All @@ -503,12 +489,10 @@ def optimize(
device=device,
)

if isinstance(loss_definition, FocalSpotLoss): # Focal-spot loss
if isinstance(loss_definition, FocalSpotLoss):
loss = flux_loss

if isinstance(
loss_definition, KLDivergenceLoss
): # Kullback-Leibler divergence loss
if isinstance(loss_definition, KLDivergenceLoss):
# Store references at epoch 0, i.e., baseline flux integral and intercept factors.
if epoch == 0:
flux_integral_reference = total_flux.sum().detach()
Expand Down Expand Up @@ -590,7 +574,6 @@ def optimize(
param.grad /= self.ddp_setup[constants.world_size] # type: ignore

optimizer.step()
# Scheduler update.
if isinstance(scheduler, torch.optim.lr_scheduler.ReduceLROnPlateau):
scheduler.step(loss.detach())
else:
Expand Down Expand Up @@ -641,7 +624,7 @@ def optimize(
"flux_integral_constraint": flux_integral_constraint_history,
"flux_integral": flux_integral,
}
log.info(f"Rank: {rank}, motor positions optimized.")
log.info(f"Rank: {rank}, aim points optimized.")

# Broadcast final motor positions for each heliostat group from source rank to others.
if self.ddp_setup["is_distributed"]:
Expand All @@ -654,7 +637,7 @@ def optimize(
src=source[indices.first_rank_from_group],
)

log.info(f"Rank: {rank}, synchronized after motor positions optimization.")
log.info(f"Rank: {rank}, synchronized after aim point optimization.")

return (
loss.detach().cpu(),
Expand Down
Loading
Loading