Skip to content
Merged
2 changes: 1 addition & 1 deletion autolens/point/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def _log_likelihood_for_coordinates(
"The number of predicted coordinates must be equal to the number of observed coordinates."
)

predicted_coordinates = set(predicted_coordinates)
predicted_coordinates = set(map(tuple, predicted_coordinates))
observed_coordinates = set(self.observed_coordinates)

log_likelihood = 0.0
Expand Down
113 changes: 77 additions & 36 deletions autolens/point/triangles/triangle_solver.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,42 @@
import logging
import math
from typing import Tuple, List
from dataclasses import dataclass
from typing import Tuple, List, Iterator

from autoarray import Grid2D, Grid2DIrregular
from autoarray.structures.triangles.subsample_triangles import SubsampleTriangles
from autoarray.structures.triangles.triangles import Triangles
from autoarray.structures.triangles.array import ArrayTriangles
from autoarray.type import Grid2DLike
from autogalaxy import OperateDeflections


logger = logging.getLogger(__name__)


@dataclass
class Step:
"""
A step in the triangle solver algorithm.

Attributes
----------
number
The number of the step.
initial_triangles
The triangles at the start of the step.
filtered_triangles
The triangles trace to triangles that contain the source plane coordinate.
neighbourhood
The neighbourhood of the filtered triangles.
up_sampled
The neighbourhood up-sampled to increase the resolution.
"""

number: int
initial_triangles: ArrayTriangles
filtered_triangles: ArrayTriangles
neighbourhood: ArrayTriangles
up_sampled: ArrayTriangles


class TriangleSolver:
def __init__(
self,
Expand Down Expand Up @@ -87,31 +112,18 @@ def solve(
-------
A list of image plane coordinates that are traced to the source plane coordinate.
"""
triangles = Triangles.for_grid(grid=self.grid)

if self.n_steps == 0:
raise ValueError(
"The target pixel scale is too large to subdivide the triangles."
)

kept_triangles = []

for _ in range(self.n_steps):
kept_triangles = self._filter_triangles(
triangles=triangles,
source_plane_coordinate=source_plane_coordinate,
)
with_neighbourhood = {
triangle
for kept_triangle in kept_triangles
for triangle in kept_triangle.neighbourhood
}
triangles = SubsampleTriangles(parent_triangles=list(with_neighbourhood))
steps = list(self.steps(source_plane_coordinate=source_plane_coordinate))
final_step = steps[-1]
kept_triangles = final_step.filtered_triangles

means = [triangle.mean for triangle in kept_triangles]
filtered_means = self._filter_low_magnification(points=means)
filtered_means = self._filter_low_magnification(points=kept_triangles.means)

difference = len(means) - len(filtered_means)
difference = len(kept_triangles.means) - len(filtered_means)
if difference > 0:
logger.debug(
f"Filtered one multiple-image with magnification below threshold."
Expand Down Expand Up @@ -152,7 +164,7 @@ def _filter_low_magnification(

def _filter_triangles(
self,
triangles: Triangles,
triangles: ArrayTriangles,
source_plane_coordinate: Tuple[float, float],
):
"""
Expand All @@ -169,16 +181,45 @@ def _filter_triangles(
-------
The triangles that contain the source plane coordinate.
"""
source_plane_grid = self._source_plane_grid(grid=triangles.grid_2d)

kept_triangles = []
for image_triangle, source_triangle in zip(
triangles.triangles,
triangles.with_updated_grid(source_plane_grid),
):
if source_triangle.contains(
point=source_plane_coordinate,
):
kept_triangles.append(image_triangle)

return kept_triangles
source_plane_grid = self._source_plane_grid(
grid=Grid2DIrregular(triangles.vertices)
)
source_triangles = triangles.with_vertices(source_plane_grid)
indexes = source_triangles.containing_indices(point=source_plane_coordinate)
return triangles.for_indexes(indexes=indexes)

def steps(
self,
source_plane_coordinate: Tuple[float, float],
) -> Iterator[Step]:
"""
Iterate over the steps of the triangle solver algorithm.

Parameters
----------
source_plane_coordinate
The source plane coordinate to trace to the image plane.

Returns
-------
An iterator over the steps of the triangle solver algorithm.
"""
initial_triangles = ArrayTriangles.for_grid(grid=self.grid)

for number in range(self.n_steps):
kept_triangles = self._filter_triangles(
initial_triangles,
source_plane_coordinate,
)
neighbourhood = kept_triangles.neighborhood()
up_sampled = neighbourhood.up_sample()

yield Step(
number=number,
initial_triangles=initial_triangles,
filtered_triangles=kept_triangles,
neighbourhood=neighbourhood,
up_sampled=up_sampled,
)

initial_triangles = up_sampled
23 changes: 23 additions & 0 deletions autolens/point/triangles/visualise.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from .triangle_solver import Step
from matplotlib import pyplot as plt
import numpy as np


def add_triangles(triangles, color):
for triangle in triangles:
triangle = np.append(triangle, [triangle[0]], axis=0)
plt.plot(triangle[:, 0], triangle[:, 1], "o-", color=color)


def visualise(step: Step):
plt.figure(figsize=(8, 8))
add_triangles(step.initial_triangles, color="black")
add_triangles(step.filtered_triangles, color="blue")
add_triangles(step.up_sampled, color="green")
add_triangles(step.neighbourhood, color="red")

plt.xlabel("X")
plt.ylabel("Y")
plt.title(f"Step {step.number}")
plt.gca().set_aspect("equal", adjustable="box")
plt.show()
4 changes: 2 additions & 2 deletions test_autolens/point/triangles/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
@pytest.fixture
def grid():
return al.Grid2D.uniform(
shape_native=(100, 100),
pixel_scales=0.05,
shape_native=(10, 10),
pixel_scales=1.0,
)
7 changes: 4 additions & 3 deletions test_autolens/point/triangles/test_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def test_solver(solver):


def test_steps(solver):
assert solver.n_steps == 3
assert solver.n_steps == 7


class NullTracer(al.Tracer):
Expand All @@ -52,6 +52,7 @@ def deflections_yx_2d_from(self, grid):
[
(0.0, 0.0),
(0.0, 1.0),
(1.0, 0.0),
(1.0, 1.0),
(0.5, 0.5),
(0.1, 0.1),
Expand All @@ -70,7 +71,7 @@ def test_trivial(
(coordinates,) = solver.solve(
source_plane_coordinate=source_plane_coordinate,
)
assert coordinates == pytest.approx(source_plane_coordinate, abs=1.0e-2)
assert coordinates == pytest.approx(source_plane_coordinate, abs=1.0e-1)


def test_real_example(grid):
Expand All @@ -97,4 +98,4 @@ def test_real_example(grid):
pixel_scale_precision=0.001,
)
result = solver.solve((0.07, 0.07))
assert len(result) == 4
assert len(result) == 5