libreda_pnr/metrics/
placement_density.rs

1// Copyright (c) 2020-2021 Thomas Kramer.
2// SPDX-FileCopyrightText: 2022 Thomas Kramer <code@tkramer.ch>
3//
4// SPDX-License-Identifier: AGPL-3.0-or-later
5
6//! Compute the placement density or placement density maps of layouts.
7
8
9use crate::db;
10use ndarray::{Array2};
11use num_traits::{Num, FromPrimitive, ToPrimitive};
12
13/// Pixelized representation of the cell locations.
14/// Cells are drawn to the DensityMap like to a rasterized image.
15/// The 2D array is the input to the FFT based electrostatic force computation.
16///
17/// Use `DensityMap::new()` and `DensityMap::from_data()` to create a DensityMap struct.
18///
19/// * `F`: Data type of coordinates.
20/// * `Z`: Data type of values.
21///
22/// # Example
23/// ```
24/// use libreda_pnr::db;
25/// use libreda_pnr::metrics::placement_density::DensityMap;
26///
27/// // Create a 'bin image' for accumulating densities.
28/// let mut c = DensityMap::new(
29///     db::Rect::new((0.0, 0.0), (10.0, 10.0)),
30///     (10, 10)
31/// );
32/// // Draw a rectangle to the density image.
33/// let r = db::Rect::new((1.0, 1.0), (2.0, 3.0));
34/// c.draw_rect(&r, 1.0);
35///
36/// // Query the density.
37/// assert_eq!(c.density_at((1.5, 1.5).into()), 1.);
38///
39/// // Directly access the density bins.
40/// // The indices may not coincide with the coordinates!
41/// // Here this is only the case because of the very specific dimension of the density map.
42/// assert_eq!(c.data[[0, 0]], 0.0);
43/// assert_eq!(c.data[[1, 1]], 1.0);
44/// assert_eq!(c.data[[2, 3]], 0.0);
45///
46/// ```
47#[derive(Clone)]
48pub struct DensityMap<F, Z> {
49    /// Offset and dimension of the drawable DensityMap.
50    pub dimension: db::Rect<F>,
51    /// Raster data of the DensityMap. Hold the sum of values, not densities.
52    pub data: Array2<Z>,
53}
54
55impl<F, Z> DensityMap<F, Z>
56    where F: Copy
57{
58    /// Consume this object and return the underlying data array.
59    pub fn get_data(self) -> Array2<Z> {
60        self.data
61    }
62
63    /// Get reference to underlying data array.
64    pub fn get_data_ref(&self) -> &Array2<Z> {
65        &self.data
66    }
67
68    /// Get mutable reference to underlying data array.
69    pub fn get_data_ref_mut(&mut self) -> &mut Array2<Z> {
70        &mut self.data
71    }
72
73    /// Get the area of the DensityMap as a rectangle.
74    pub fn dimension(&self) -> db::Rect<F> {
75        self.dimension
76    }
77
78    /// Get the number of bins in `x` and `y` direction.
79    fn num_bins(&self) -> (usize, usize) {
80        self.data.dim()
81    }
82}
83
84impl<F, Z> DensityMap<F, Z>
85    where F: Copy + Num + PartialOrd + FromPrimitive + ToPrimitive,
86          Z: Copy + std::ops::Div<F, Output=Z> {
87    /// Read the density at a given coordinate `p`.
88    /// The values are interpolated by the 'nearest neighbour' strategy.
89    ///
90    /// # Panics
91    /// Panics when the point `p` is outside of the defined area of the density map.
92    pub fn density_at(&self, p: db::Point<F>) -> Z {
93        self.get_density_at(p).expect("Point `p` is outside of the defined region.")
94    }
95
96
97    /// Read the density at a given coordinate `p`.
98    /// The values are interpolated by the 'nearest neighbour' strategy.
99    ///
100    /// Returns `None` if `p` is outside of the defined region.
101    pub fn get_density_at(&self, p: db::Point<F>) -> Option<Z> {
102        self.get_value_at(p)
103            .map(|v| v / self.bin_area())
104    }
105}
106
107impl<F, Z> DensityMap<F, Z>
108    where F: Copy + Num + FromPrimitive + ToPrimitive + PartialOrd,
109          Z: Copy {
110    /// Get real dimension (width, height) of a bin.
111    pub fn bin_dimension(&self) -> (F, F) {
112        let (w, h) = self.num_bins();
113        let bin_width = self.dimension.width() / F::from_usize(w).unwrap();
114        let bin_height = self.dimension.height() / F::from_usize(h).unwrap();
115        (bin_width, bin_height)
116    }
117
118    /// Get the area of a bin.
119    pub fn bin_area(&self) -> F {
120        let (w, h) = self.bin_dimension();
121        w * h
122    }
123
124    /// Convert a coordinate into array indices.
125    pub fn coordinates_to_indices(&self, p: db::Point<F>) -> (usize, usize) {
126        let r = self.dimension;
127        assert!(r.contains_point(p), "Point is not inside boundary.");
128        let (w, h) = self.data.dim();
129        let x = (p.x - r.lower_left.x) * F::from_usize(w).unwrap() / r.width();
130        let y = (p.y - r.lower_left.y) * F::from_usize(h).unwrap() / r.height();
131        (x.to_usize().unwrap(),
132         y.to_usize().unwrap())
133    }
134
135    /// Read the accumulated value at a given coordinate `p`.
136    /// The values are interpolated by the 'nearest neighbour' strategy.
137    ///
138    /// # Panics
139    /// Panics when the point `p` is outside of the defined area of the density map.
140    /// `get_value_at()` returns an `Option` instead of panicking.
141    pub fn value_at(&self, p: db::Point<F>) -> Z {
142        self.get_value_at(p).expect("Point `p` is out of the defined region.")
143    }
144
145    /// Read the accumulated value at a given coordinate `p`.
146    /// The values are interpolated by the 'nearest neighbour' strategy.
147    /// Returns `None` if `p` is outside of the map region.
148    pub fn get_value_at(&self, p: db::Point<F>) -> Option<Z> {
149        if self.dimension.contains_point(p) {
150            let (x, y) = self.coordinates_to_indices(p);
151            Some(self.data[[x, y]])
152        } else {
153            None
154        }
155    }
156}
157
158impl<F, Z> DensityMap<F, Z>
159    where F: Copy + Num + PartialOrd + FromPrimitive + ToPrimitive,
160          Z: Num + std::ops::AddAssign + Copy + Clone + std::ops::Mul<F, Output=Z> {
161
162    /// Create an all-zero `w`x`h` array.
163    /// `r` defines the spanned region in the euclidean plane.
164    pub fn new(r: db::Rect<F>, (w, h): (usize, usize)) -> Self {
165        Self {
166            dimension: r,
167            data: Array2::zeros((w, h)),
168        }
169    }
170
171    /// Create a DensityMap from existing data.
172    /// `r` defines the spanned region in the euclidean plane.
173    pub fn from_data(r: db::Rect<F>, data: Array2<Z>) -> Self {
174        Self {
175            dimension: r,
176            data,
177        }
178    }
179
180    /// Set all values to zero.
181    pub fn clear(&mut self) {
182        self.data.iter_mut().for_each(|x| *x = Z::zero());
183    }
184
185    /// Get the location of the lower left corner of bin with index `[x, y]`.
186    fn bin_lower_left_corner(&self, (x, y): (usize, usize)) -> db::Point<F> {
187        let (bin_width, bin_height) = self.bin_dimension();
188        // Lower left corner of the bin.
189        let (x, y) = (F::from_usize(x).unwrap(), F::from_usize(y).unwrap());
190        self.dimension.lower_left() + db::Point::new(x * bin_width, y * bin_height)
191    }
192
193    /// Get the location of the center of bin with index `[x, y]`.
194    pub fn bin_center(&self, (x, y): (usize, usize)) -> db::Point<F> {
195        let (bin_width, bin_height) = self.bin_dimension();
196        let _2 = F::one() + F::one();
197        let bin_center = db::Point::new(bin_width / _2, bin_height / _2);
198        self.bin_lower_left_corner((x, y)) + bin_center
199    }
200
201    /// Get the rectangle shape of the bin at index `(i, j)`.
202    pub fn get_bin_shape(&self, (x, y): (usize, usize)) -> db::Rect<F> {
203
204        // Lower left corner of the bin
205        let start = self.bin_lower_left_corner((x, y));
206        // Upper right corner of the bin.
207        let (bin_width, bin_height) = self.bin_dimension();
208        let end = start + db::Point::new(bin_width, bin_height);
209
210        db::Rect::new(start, end)
211    }
212
213    /// Draw the rectangle `r` to the DensityMap by adding the `value`
214    /// to all bins that interact with `r`. If a bin overlaps only partially
215    /// with `r` then `a*value` is added to it where `a` is the fraction of the overlap.
216    pub fn draw_rect(&mut self, r: &db::Rect<F>, value: Z) {
217
218        // Crop rectangle to dimension of density bins.
219        if let Some(r) = r.intersection(&self.dimension)
220        {
221
222            // Find indices of bins that interact with the corners of the rectangle `r`.
223            let (xstart, ystart) = self.coordinates_to_indices(r.lower_left);
224            let (xend, yend) = self.coordinates_to_indices(r.upper_right);
225
226            let xend = xend.min(self.data.dim().0-1);
227            let yend = yend.min(self.data.dim().1-1);
228
229            let bin_area = self.bin_area();
230            // Loop over all bins that interact with the rectangle `r`.
231            for x in xstart..xend+1 {
232                for y in ystart..yend+1 {
233                    if x == xstart || x == xend || y == ystart || y == yend {
234                        // Corner cases.
235                        // Compute increment by the overlap of the bin shape and the rectangle.
236                        let bin_shape = self.get_bin_shape((x, y));
237                        let overlap_area = bin_shape.intersection(&r)
238                            .map(|r| r.width() * r.height())
239                            .unwrap_or(F::zero());
240                        self.data[[x, y]] += value * overlap_area;
241                    } else {
242                        self.data[[x, y]] += value * bin_area;
243                    }
244                }
245            }
246        }
247    }
248}
249
250
251impl<F, Z> DensityMap<F, Z>
252    where F: Copy + Num + PartialOrd + FromPrimitive + ToPrimitive,
253          Z: Num + std::ops::AddAssign + Copy + Clone + std::ops::Mul<F, Output=Z> + FromPrimitive {
254
255    /// Create a density map with lower resolution.
256    ///
257    /// Down-sampling is done by creating `n*n` bins. Therefore the `reduction_factor` must
258    /// divide the number of bins in both x and y direction.
259    pub fn downsample(&self, reduction_factor: usize) -> Self {
260        assert!(reduction_factor >= 1);
261        let (w, h) = (self.data.shape()[0], self.data.shape()[1]);
262        assert_eq!(w % reduction_factor, 0, "Dimension must be divisible by the reduction factor.");
263        assert_eq!(h % reduction_factor, 0, "Dimension must be divisible by the reduction factor.");
264
265        let w_new = w / reduction_factor;
266        let h_new = h / reduction_factor;
267
268        let mut new_data = Array2::zeros((w_new, h_new));
269
270        for x in 0..w {
271            let x_new = x / reduction_factor;
272            for y in 0..h {
273                let y_new = y / reduction_factor;
274
275                new_data[[x_new, y_new]] += self.data[[x, y]];
276            }
277        }
278
279        // // Normalize
280        // let f = Z::from_usize(reduction_factor * reduction_factor).unwrap();
281        // new_data.iter_mut()
282        //     .for_each(|d| *d = *d / f);
283
284        Self::from_data(self.dimension, new_data)
285    }
286}
287
288    #[test]
289fn test_coordinates_to_indices() {
290    let c: DensityMap<_, f64> = DensityMap::new(db::Rect::new((0.0, 0.0),
291                                                              (10.0, 20.0)), (10, 20));
292    assert_eq!(c.coordinates_to_indices(db::Point::new(0.0, 0.0)), (0, 0));
293    assert_eq!(c.coordinates_to_indices(db::Point::new(10.0, 20.0)), (10, 20));
294    assert_eq!(c.coordinates_to_indices(db::Point::new(0.5, 0.5)), (0, 0));
295}
296
297#[test]
298fn test_bin() {
299    let c: DensityMap<_, f64> = DensityMap::new(db::Rect::new((0.0, 0.0),
300                                                              (10.0, 20.0)), (10, 10));
301    assert_eq!(c.num_bins(), (10, 10));
302    assert_eq!(c.get_bin_shape((0, 0)), db::Rect::new((0.0, 0.0), (1.0, 2.0)));
303}
304
305#[test]
306fn test_draw_rect() {
307
308    use db::traits::DoubledOrientedArea;
309
310    let mut c = DensityMap::new(db::Rect::new((0.0, 0.0),
311                                              (10.0, 10.0)), (10, 10));
312    let r = db::Rect::new((1.0, 1.0), (2.0, 3.0));
313    c.draw_rect(&r, 1.0);
314    assert_eq!(c.data[[0, 0]], 0.0);
315    assert_eq!(c.data[[1, 1]], 1.0);
316    assert_eq!(c.data[[2, 3]], 0.0);
317
318    let sum: f64 = c.data.iter().sum();
319    assert_eq!(
320        2.0 * sum,
321        r.area_doubled_oriented()
322    );
323}
324
325#[test]
326fn test_draw_rect_with_partial_bins() {
327
328    use db::traits::DoubledOrientedArea;
329
330    let mut c = DensityMap::new(db::Rect::new((0.0, 0.0),
331                                              (10.0, 10.0)), (10, 10));
332    let r = db::Rect::new((1.5, 1.5), (5.25, 6.0));
333    c.draw_rect(&r, 1.0);
334    assert_eq!(c.data[[0, 0]], 0.0);
335    assert_eq!(c.data[[1, 1]], 0.25);
336    assert_eq!(c.data[[1, 2]], 0.5);
337    assert_eq!(c.data[[2, 2]], 1.0);
338
339    // Total sum of DensityMap must correspond to the total value of the drawn rectangle.
340    let sum: f64 = c.data.iter().sum();
341    assert_eq!(
342        2.0 * sum,
343        r.area_doubled_oriented()
344    );
345}
346
347
348#[test]
349fn test_draw_oversize_rect() {
350    let mut c = DensityMap::new(db::Rect::new((0.0, 0.0),
351                                              (10.0, 10.0)), (10, 10));
352    let r = db::Rect::new((-1.0, -1.0), (11.0, 11.0));
353    c.draw_rect(&r, 1.0);
354    assert_eq!(c.data[[0, 0]], 1.0);
355    assert_eq!(c.data[[9, 9]], 1.0);
356
357}
358
359#[test]
360fn test_draw_oversize_rect_1x1() {
361    let mut c = DensityMap::new(db::Rect::new((0.0, 0.0),
362                                              (10.0, 10.0)), (1, 1));
363    let r = db::Rect::new((-1.0, -1.0), (11.0, 11.0));
364    c.draw_rect(&r, 1.0);
365    assert_eq!(c.density_at((0.0, 0.0).into()), 1.0);
366    assert_eq!(c.data[[0, 0]], 100.0);
367
368}