simcats 1.1.0__py3-none-any.whl → 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. simcats/__init__.py +4 -3
  2. simcats/_default_configs.py +129 -13
  3. simcats/_simulation.py +451 -69
  4. simcats/config_samplers/_GaAs_v1_random_variations_v3_config_sampler.py +1059 -0
  5. simcats/config_samplers/__init__.py +9 -0
  6. simcats/distortions/_distortion_interfaces.py +1 -1
  7. simcats/distortions/_dot_jumps.py +8 -6
  8. simcats/distortions/_random_telegraph_noise.py +4 -4
  9. simcats/distortions/_transition_blurring.py +5 -5
  10. simcats/distortions/_white_noise.py +2 -2
  11. simcats/ideal_csd/geometric/_generate_lead_transition_mask.py +3 -3
  12. simcats/ideal_csd/geometric/_get_electron_occupation.py +5 -5
  13. simcats/ideal_csd/geometric/_ideal_csd_geometric.py +5 -5
  14. simcats/ideal_csd/geometric/_ideal_csd_geometric_class.py +9 -9
  15. simcats/ideal_csd/geometric/_tct_bezier.py +5 -5
  16. simcats/sensor/__init__.py +10 -6
  17. simcats/sensor/{_generic_sensor.py → _sensor_generic.py} +1 -1
  18. simcats/sensor/_sensor_interface.py +164 -11
  19. simcats/sensor/_sensor_rise_glf.py +229 -0
  20. simcats/sensor/_sensor_scan_sensor_generic.py +929 -0
  21. simcats/sensor/barrier_function/__init__.py +9 -0
  22. simcats/sensor/barrier_function/_barrier_function_glf.py +280 -0
  23. simcats/sensor/barrier_function/_barrier_function_interface.py +43 -0
  24. simcats/sensor/barrier_function/_barrier_function_multi_glf.py +157 -0
  25. simcats/sensor/deformation/__init__.py +9 -0
  26. simcats/sensor/deformation/_sensor_peak_deformation_circle.py +109 -0
  27. simcats/sensor/deformation/_sensor_peak_deformation_interface.py +65 -0
  28. simcats/sensor/deformation/_sensor_peak_deformation_linear.py +77 -0
  29. simcats/support_functions/__init__.py +11 -3
  30. simcats/support_functions/_generalized_logistic_function.py +146 -0
  31. simcats/support_functions/_linear_algebra.py +171 -0
  32. simcats/support_functions/_parameter_sampling.py +108 -19
  33. simcats/support_functions/_pixel_volt_transformation.py +24 -0
  34. simcats/support_functions/_reset_offset_mu_sens.py +43 -0
  35. {simcats-1.1.0.dist-info → simcats-2.0.0.dist-info}/METADATA +93 -29
  36. simcats-2.0.0.dist-info/RECORD +53 -0
  37. {simcats-1.1.0.dist-info → simcats-2.0.0.dist-info}/WHEEL +1 -1
  38. simcats-1.1.0.dist-info/RECORD +0 -37
  39. /simcats/sensor/{_gaussian_sensor_peak.py → _sensor_peak_gaussian.py} +0 -0
  40. /simcats/sensor/{_lorentzian_sensor_peak.py → _sensor_peak_lorentzian.py} +0 -0
  41. {simcats-1.1.0.dist-info → simcats-2.0.0.dist-info/licenses}/LICENSE +0 -0
  42. {simcats-1.1.0.dist-info → simcats-2.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,65 @@
1
+ """This module contains the interface for simulating deformations of sensor peaks in scans.
2
+
3
+ @author: b.papajewski
4
+ """
5
+
6
+ from abc import ABC, abstractmethod
7
+ from typing import Union
8
+
9
+ import numpy as np
10
+
11
+ __all__ = []
12
+
13
+ from simcats.sensor import SensorInterface
14
+
15
+
16
+ class SensorPeakDeformationInterface(ABC):
17
+ """General interface for sensor peak deformations.
18
+
19
+ Classes that implement this abstract class realize different types of deformation of a single wavefront.
20
+ """
21
+
22
+ @abstractmethod
23
+ def calc_mu(self, dist: Union[float, np.ndarray], mu0: float) -> float:
24
+ """
25
+ Method that calculates the deformed potential value of mu0 of a sensor peak that is moved based on
26
+ the distance (`dist`) to a middle line from which the deformation originates. The middle line follows
27
+ the direction of the sensor peaks (i.e., the sensor dot potential) and is perpendicular to the wavefronts
28
+ defined by the peaks.
29
+
30
+ Args:
31
+ dist (Union[float, np.ndarray]): Distance from the middle line, from which the deformation originates. If a
32
+ point is above the middle line the distance is positive and if it is below, the distance is negative.
33
+ The parameter can either be a np.ndarray that contains multiple distances or a single distance as a
34
+ float.
35
+ mu0 (float): Potential offset for calculation of the deformed potential. Usually this should be the
36
+ potential offset of the sensor peak, that should be deformed.
37
+
38
+ Returns:
39
+ Union[float, np.ndarray]: Deformed potential value mu0 of a sensor peak. The same type as the type of
40
+ `dist` is returned.
41
+ """
42
+ raise NotImplementedError
43
+
44
+ @property
45
+ def sensor(self) -> SensorInterface:
46
+ """
47
+ Returns the sensor object to which the deformed sensor peak belongs.
48
+
49
+ Returns:
50
+ SensorInterface: Sensor object of the deformed sensor peak.
51
+ """
52
+ if self.__sensor:
53
+ return self.__sensor
54
+ else:
55
+ return None
56
+
57
+ @sensor.setter
58
+ def sensor(self, sensor: SensorInterface) -> None:
59
+ """
60
+ Sets the sensor object to which the deformed sensor peak belongs.
61
+
62
+ Args:
63
+ sensor (SensorInterface): Sensor object of the deformed sensor peak.
64
+ """
65
+ self.__sensor = sensor
@@ -0,0 +1,77 @@
1
+ """This module contains the class for simulating a linear deformation of sensor peaks in a scan.
2
+
3
+ @author: b.papajewski
4
+ """
5
+ import math
6
+ from typing import Union
7
+
8
+ import numpy as np
9
+
10
+ from simcats.sensor.deformation import SensorPeakDeformationInterface
11
+
12
+ __all__ = []
13
+
14
+
15
+ class SensorPeakDeformationLinear(SensorPeakDeformationInterface):
16
+ """Linear deformation implementation of the SensorPeakDeformationInterface."""
17
+
18
+ def __init__(self, angle: float) -> None:
19
+ """Initialization of an object of the class LinearSensorPeakDeformation.
20
+ This kind of deformation uses a single line for the deformation of a sensor peak (tilting the wavefront).
21
+
22
+ Args:
23
+ angle (float): Angle of the line that is used for the linear deformation of the sensor peak. This Angle
24
+ is specified in radians. The angle between the two straight lines is specified, which lies above the
25
+ deformation line and to the right of the middle line.
26
+ """
27
+ super().__init__()
28
+
29
+ self.angle = angle
30
+
31
+ @property
32
+ def angle(self) -> float:
33
+ """Returns the angle of the line used for deforming the sensor peak.
34
+
35
+ Returns:
36
+ float: Angle of the line that is used for the linear deformation of the sensor peak.
37
+ """
38
+ return self.__angle
39
+
40
+ @angle.setter
41
+ def angle(self, angle: float):
42
+ self.__angle = angle
43
+
44
+ def calc_mu(self, dist: Union[float, np.ndarray], mu0: float) -> Union[float, np.ndarray]:
45
+ """
46
+ Method that calculates the deformed potential value for mu0 of mu0 of a sensor peak that is moved based on the
47
+ distance (`dist`) to a middle line, from which the deformation originates, using a line. The middle line follows
48
+ the direction of the sensor peaks (i.e., the sensor dot potential) and is perpendicular to the wavefronts
49
+ defined by the peaks.
50
+
51
+ For the deformation, the potential value of the point of the line that has the given distance to the center line
52
+ is used.
53
+
54
+ Args:
55
+ dist (Union[float, np.ndarray]): Distance from the middle line, from which the deformation originates. If a
56
+ point is above the middle line the distance is positive and if it is below, the distance is negative.
57
+ The parameter can either be a np.ndarray that contains multiple distances or a single distance as a
58
+ float.
59
+ mu0 (float): Potential offset for calculation of the deformed potential. Usually this should be the
60
+ potential offset of the sensor peak, that should be deformed.
61
+
62
+ Returns:
63
+ Union[float, np.ndarray]: Deformed potential value mu0 of a sensor peak. The same type as the type of
64
+ `dist` is returned.
65
+ """
66
+
67
+ # Calculation of the gradient of the deformation line
68
+ m = 1 / math.tan(self.angle)
69
+
70
+ dif = m * dist
71
+ dif *= np.linalg.norm(self.sensor.alpha_sensor_gate[0])
72
+ return mu0 + dif
73
+
74
+ def __repr__(self):
75
+ return (
76
+ self.__class__.__name__ + f"(angle={self.angle})"
77
+ )
@@ -3,14 +3,22 @@ SimCATS subpackage with support functions that are not assigned to any specific
3
3
  """
4
4
 
5
5
  from simcats.support_functions._parameter_sampling import ParameterSamplingInterface, NormalSamplingRange, \
6
- LogNormalSamplingRange, UniformSamplingRange
6
+ LogNormalSamplingRange, UniformSamplingRange, ExponentialSamplingRange
7
7
  from simcats.support_functions._fermi_filter1d import fermi_filter1d, fermi_dirac_derivative
8
8
  from simcats.support_functions._cumulative_distribution_functions import cauchy_cdf, multi_cauchy_cdf, sigmoid_cdf, \
9
9
  multi_sigmoid_cdf
10
+ from simcats.support_functions._generalized_logistic_function import glf, inverse_glf, multi_glf
11
+ from simcats.support_functions._reset_offset_mu_sens import reset_offset_mu_sens
10
12
  from simcats.support_functions._signed_dist_points_line import signed_dist_points_line
13
+ from simcats.support_functions._linear_algebra import line_line_intersection
14
+ from simcats.support_functions._linear_algebra import line_circle_intersection
15
+ from simcats.support_functions._linear_algebra import is_point_below_line
16
+ from simcats.support_functions._pixel_volt_transformation import pixel_to_volt_1d
11
17
  from simcats.support_functions._rotate_points import rotate_points
12
18
  from simcats.support_functions._plotting import plot_csd
13
19
 
14
20
  __all__ = ["ParameterSamplingInterface", "NormalSamplingRange", "LogNormalSamplingRange", "UniformSamplingRange",
15
- "fermi_filter1d", "fermi_dirac_derivative", "cauchy_cdf", "multi_cauchy_cdf", "sigmoid_cdf",
16
- "multi_sigmoid_cdf", "signed_dist_points_line", "rotate_points", "plot_csd"]
21
+ "ExponentialSamplingRange", "fermi_filter1d", "fermi_dirac_derivative", "cauchy_cdf", "multi_cauchy_cdf",
22
+ "sigmoid_cdf", "multi_sigmoid_cdf", "glf", "inverse_glf", "multi_glf", "reset_offset_mu_sens",
23
+ "signed_dist_points_line", "line_line_intersection", "line_circle_intersection", "is_point_below_line",
24
+ "pixel_to_volt_1d", "rotate_points", "plot_csd"]
@@ -0,0 +1,146 @@
1
+ """This module contains an implementation of the generalized logistic function (GLF) used for sensor barriers.
2
+
3
+ @author: b.papajewski
4
+ """
5
+
6
+ from typing import Union
7
+
8
+ import numpy as np
9
+
10
+
11
+ def glf(potential: Union[float, np.ndarray],
12
+ asymptote_left: float,
13
+ asymptote_right: float,
14
+ growth_rate: float,
15
+ asymmetry: float,
16
+ shape_factor: float,
17
+ denominator_offset: float = 1,
18
+ offset: float = 0
19
+ ) -> Union[float, np.ndarray]:
20
+ """Function implementing the generalized logistic function.
21
+ For further information see: https://en.wikipedia.org/wiki/Generalised_logistic_function
22
+
23
+ Args:
24
+ potential (Union[float, np.ndarray]): Originally called t. The potential is the variable of the GLF for which
25
+ the value of the function should be calculated.
26
+ asymptote_left (float): Originally called A. This parameter is the left horizontal asymptote of the function.
27
+ Any rational number can be used as the left asymptote. This parameter may take any rational number.
28
+ asymptote_right (float): Originally called K. Specifies the right horizontal asymptote of the function
29
+ when denominator_offset=1. If asymptote_left=0 and denominator_offset=1 then this parameter is also called
30
+ the carrying capacity. This parameter may take any rational number.
31
+ growth_rate (float): Originally called B. The growth rate of the function. The value must be a float and can be
32
+ any rational number. Be careful with negative values, because the function is mirrored on a vertical
33
+ straight line for these. This line passes through the point where the potential equals `offset`.
34
+ asymmetry (float): Originally called nu. This parameter introduces skew and affects symmetry. It also affects
35
+ near which asymptote maximum growth occurs. The value of asymmetry must be a rational number greater than
36
+ zero. \n
37
+ - `asymmetry > 1`: the curve rises more gradually before the midpoint and more sharply after. \n
38
+ - `asymmetry < 1`: the curve rises quickly early on and levels off more slowly.
39
+ shape_factor (float): Originally called Q. is related to the value Y(0) and adjusts the curve’s value at the
40
+ y-intercept. Thereby it changes the shape of the function without changing the asymptotes. The shape factor
41
+ can be any rational number.
42
+ denominator_offset (float): Originally called C. A constant added to the denominator inside the power. Controls
43
+ the initial level of the denominator.This parameter must be a rational number.
44
+ It typically takes a value of 1. Otherwise, the upper asymptote is
45
+ asymptote_left + (asymptote_right-asymptote_left)/(denominator_offset^(1/asymmetry)).
46
+ offset (float): Parameter that shifts the function starting from the zero point. If the offset is positive, the
47
+ function is shifted to the right and if it is negative, it is shifted to the left.
48
+
49
+ Returns:
50
+ Union[float, np.ndarray]: Value of the GLF at the given potential (originally time t) for a given set of
51
+ parameters. The returned datatype is the same as the type of `potential`.
52
+ """
53
+
54
+ return asymptote_left + ((asymptote_right - asymptote_left) / (
55
+ np.power(denominator_offset + shape_factor * np.exp(-growth_rate * (potential - offset)), (1 / asymmetry))))
56
+
57
+
58
+ def inverse_glf(
59
+ value: Union[float, np.ndarray],
60
+ asymptote_left: float,
61
+ asymptote_right: float,
62
+ growth_rate: float,
63
+ asymmetry: float,
64
+ shape_factor: float,
65
+ denominator_offset: float = 1,
66
+ offset: float = 0
67
+ ) -> Union[float, np.ndarray]:
68
+ """
69
+ Inverse of the function `glf` which computes the inverse of a generalized logistic function for a set of parameters.
70
+
71
+ Args:
72
+ value: The input value(s) for which to compute the inverse GLF. Can be a single float or a numpy array.
73
+ asymptote_left (float): Originally called A. This parameter is the left horizontal asymptote of the function.
74
+ Any rational number can be used as the left asymptote. This parameter may take any rational number.
75
+ asymptote_right (float): Originally called K. Specifies the right horizontal asymptote of the function
76
+ when denominator_offset=1. If asymptote_left=0 and denominator_offset=1 then this parameter is also called
77
+ the carrying capacity. This parameter may take any rational number.
78
+ growth_rate (float): Originally called B. The growth rate of the function. The value must be a float and can be
79
+ any rational number. Be careful with negative values, because the function is mirrored on a vertical
80
+ straight line for these. This line passes through the point where the potential equals `offset`.
81
+ asymmetry (float): Originally called nu. This parameter introduces skew and affects symmetry. It also affects
82
+ near which asymptote maximum growth occurs. The value of asymmetry must be a rational number greater than
83
+ zero. \n
84
+ - `asymmetry > 1`: the curve rises more gradually before the midpoint and more sharply after. \n
85
+ - `asymmetry < 1`: the curve rises quickly early on and levels off more slowly.
86
+ shape_factor (float): Originally called Q. is related to the value Y(0) and adjusts the curve’s value at the
87
+ y-intercept. Thereby it changes the shape of the function without changing the asymptotes. The shape factor
88
+ can be any rational number.
89
+ denominator_offset (float): Originally called C. A constant added to the denominator inside the power. Controls
90
+ the initial level of the denominator.This parameter must be a rational number.
91
+ It typically takes a value of 1. Otherwise, the upper asymptote is \n
92
+ asymptote_left + (asymptote_right-asymptote_left)/(denominator_offset^(1/asymmetry)).
93
+ offset (float): Parameter that shifts the function starting from the zero point. If the offset is positive, the
94
+ function is shifted to the right and if it is negative, it is shifted to the left.
95
+
96
+ Returns:
97
+ Union[float, np.ndarray]: Potential (originally time t) of the inverse GLF at the given value for a given set of
98
+ parameters. The returned datatype is the same as the type of `value`.
99
+ """
100
+ return (growth_rate * offset - np.log((((asymptote_left - asymptote_right) / (
101
+ asymptote_left - value)) ** asymmetry - denominator_offset) / shape_factor)) / growth_rate
102
+
103
+
104
+ def multi_glf(potential: Union[float, np.ndarray], *params: float) -> Union[float, np.ndarray]:
105
+ """Function that combines several GLFs as a sum into one function.
106
+ For further information see: https://en.wikipedia.org/wiki/Generalised_logistic_function
107
+
108
+ Each GLF has the following parameters: \n
109
+ - asymptote_left (float): Originally called A. This parameter is the left horizontal asymptote of the function.
110
+ Any rational number can be used as the left asymptote.
111
+ - asymptote_right (float): Originally called K. Specifies the right horizontal asymptote of the function
112
+ when denominator_offset=1. If asymptote_left=0 and denominator_offset=1 then this parameter is also called the
113
+ carrying capacity. This parameter may take any rational number.
114
+ - growth_rate (float): Originally called B. The growth rate of the function. The value must be a float and can be
115
+ any rational number. Be careful with negative values, because the function is mirrored on a vertical straight
116
+ line for these. This line passes through the point where the potential equals `offset`.
117
+ - asymmetry (float): Originally called nu. This parameter introduces skew and affects symmetry. It also affects
118
+ near which asymptote maximum growth occurs. The value of asymmetry must be a rational number greater than zero. \n
119
+ - If `asymmetry > 1`: the curve rises more gradually before the midpoint and more sharply after. \n
120
+ - If `asymmetry < 1`: the curve rises quickly early on and levels off more slowly.
121
+ - shape_factor (float): Originally called Q. is related to the value Y(0) and adjusts the curve’s value at the
122
+ y-intercept. Thereby it changes the shape of the function without changing the asymptotes. The shape factor
123
+ can be any rational number.
124
+ - denominator_offset (float): Originally called C. A constant added to the denominator inside the power. Controls
125
+ the initial level of the denominator.This parameter must be a rational number.
126
+ It typically takes a value of 1. Otherwise, the upper asymptote is \n
127
+ asymptote_left + (asymptote_right - asymptote_left) / (denominator_offset^(1 / asymmetry)).
128
+ - offset (float): Potential offset, that shifts the function starting from the zero point. If the offset is
129
+ positive, the function is shifted to the right and if it is negative, it is shifted to the left.
130
+
131
+ The number of GLFs is specified by the number of parameters. To do this, the parameter count must be divisible by
132
+ seven and a GLF is added for every seven other parameters.
133
+
134
+ Args:
135
+ potential (Union[float, np.ndarray]): Originally called t. The potential is the variable of the GLF for which
136
+ the value of the function should be calculated.
137
+ *params: Additional positional arguments representing the GLF parameters. The number of additional parameters
138
+ must be divisible by seven and determines the number of GLFs that are used for the Multi-GLF. All parameters
139
+ consist of sequential groups of seven floats that each represent a single GLF. All individual parameters are
140
+ described above and are in the same order as they are described.
141
+
142
+ Returns:
143
+ Union[float, np.ndarray]: Value of the GLF at the given potential (originally time t).
144
+ """
145
+ assert not (len(params) % 7)
146
+ return sum([glf(potential, *params[i: i + 7]) for i in range(0, len(params), 7)])
@@ -0,0 +1,171 @@
1
+ """This module provides functionality based on linear algebra for evaluating geometric relationships, such as between
2
+ two lines or between a point and a line.
3
+
4
+ @author: b.papajewski
5
+ """
6
+
7
+ import numbers
8
+ from typing import Union, Sequence, Tuple, List
9
+
10
+ import numpy as np
11
+
12
+
13
+ def is_point_below_line(point: Tuple[float, float], line: Union[Tuple[float, float], float]) -> bool:
14
+ """Method for evaluating whether a point is below a line.
15
+
16
+ Args:
17
+ point (Tuple[float, float]): Point to be evaluated.
18
+ line (Union[Tuple[float, float], float]: Either a straight line (m*x+b) given as a tuple (m,b) or as a single
19
+ value if it is a horizontal line. This then specifies the height b of the line.
20
+
21
+ Returns:
22
+ bool: True if the point is below the line, False otherwise.
23
+ """
24
+ x, y = point
25
+
26
+ # If the straight line is specified as a single float, it is a horizontal line
27
+ if isinstance(line, int):
28
+ b = line
29
+ return y < b
30
+ # If the straight line is specified as a tuple (m, b)
31
+ elif isinstance(line, tuple) and len(line) == 2:
32
+ m, b = line
33
+ y_line = m * x + b
34
+ return y < y_line
35
+ else:
36
+ raise ValueError(
37
+ "The straight line must be specified either as a tuple (m, b) or as a single integer for a horizontal line.")
38
+
39
+
40
+ def line_line_intersection(line1: Union[Sequence, np.ndarray, numbers.Real],
41
+ line2: Union[Sequence, np.ndarray, numbers.Real]):
42
+ """Method for calculating the intersection of two lines.
43
+ Both lines can be specified either by a straight line equation (m*x+b) or by a single number if it is a vertical
44
+ line. The single number gives the x coordinate of the vertical line.
45
+
46
+ Args:
47
+ line1 (Union[Sequence, np.ndarray, numbers.Real]): A two element sequence when it's a straight line equation
48
+ (m,b) or a single number when line1 is a vertical line. In this case, the number indicates the x coordinate
49
+ of the vertical line.
50
+ line2 (Union[Sequence, np.ndarray, numbers.Real]): A two element sequence when it's a straight line equation
51
+ (m,b) or a single number when line1 is a vertical line. In this case, the number indicates the x coordinate
52
+ of the vertical line.
53
+
54
+ Returns:
55
+ The x and y coordinates of the intersection of the two lines. If there is no intersection, an exception
56
+ is thrown.
57
+ """
58
+ for i, line in enumerate((line1, line2), start=1):
59
+ if not (isinstance(line, numbers.Real) or len(line) == 2):
60
+ raise ValueError(f"Invalid parameter - line{i} must either be a single number or an sequence with two elements!")
61
+
62
+ # Both lines are straight line equations
63
+ if isinstance(line1, (Sequence, np.ndarray)) and isinstance(line2, (Sequence, np.ndarray)):
64
+ m1, b1 = line1
65
+ m2, b2 = line2
66
+
67
+ # Check if lines are parallel
68
+ if m1 == m2:
69
+ if b1 == b2:
70
+ raise ValueError("The lines are the same.")
71
+ else:
72
+ raise ValueError("The lines are parallel and do not intersect.")
73
+
74
+ # Compute intersection point
75
+ x = (b2 - b1) / (m1 - m2)
76
+ y = m1 * x + b1
77
+
78
+ return np.array([x, y])
79
+
80
+ # Line1 is a straight line equation and line2 is a vertical line
81
+ elif isinstance(line1, (Sequence, np.ndarray)):
82
+ m1, b1 = line1
83
+ return np.array([line2, m1 * line2 + b1])
84
+
85
+ # Line1 is a vertical line and line2 is a straight line equation
86
+ elif isinstance(line2, (Sequence, np.ndarray)):
87
+ m2, b2 = line2
88
+ return np.array([line1, m2 * line1 + b2])
89
+
90
+ # Both lines are vertical lines und therefore also parallel
91
+ else:
92
+ if line1 == line2:
93
+ raise ValueError("The lines are the same.")
94
+ else:
95
+ raise ValueError("The lines are parallel and do not intersect.")
96
+
97
+
98
+ def line_circle_intersection(line: Union[float, Tuple], circle_center: Tuple, radius: float):
99
+ """Method for calculating the intersection of a line and a circle.
100
+
101
+ Args:
102
+ line (Union(float,Tuple)): The line with which the intersection points are to be calculated. This line can
103
+ either be given as a tuple with the slope and y-intercept in the form (m,b) or as a float if it is a
104
+ vertical line.
105
+ circle_center (Tuple): The center of the circle. The center is specified as a tuple (x-center, y-center).
106
+ radius (float): The radius of the circle. The radius is specified as float that can be any rational number
107
+ greater than 0.
108
+
109
+ Returns:
110
+ List[Tuple]: This method returns a list of the intersection points of the line and a circle. The points are
111
+ returned as a tuple with the form (x-coordinate,y-coordinate). The list of intersection points can contain zero
112
+ to two tuples. Accordingly, an empty list is returned if the line and the circle have no intersection.
113
+ """
114
+ if radius <= 0:
115
+ raise ValueError("The radius of the circle must be greater than 0.")
116
+
117
+ center_x, center_y = circle_center
118
+
119
+ # Check whether the line is a vertical line
120
+ if isinstance(line, tuple):
121
+ m, b = line
122
+
123
+ # Calculation of the coefficients of the quadratic equation system
124
+ A = 1 + m ** 2
125
+ B = 2 * (m * b - m * center_y - center_x)
126
+ C = center_x ** 2 + center_y ** 2 + b ** 2 - 2 * b * center_y - radius ** 2
127
+
128
+ # Calculation of the discriminant
129
+ discriminant = B ** 2 - 4 * A * C
130
+
131
+ if discriminant < 0:
132
+ # No intersection
133
+ return []
134
+ elif discriminant == 0:
135
+ # Single intersection
136
+ x = -B / (2 * A)
137
+ y = m * x + b
138
+ return [(x, y)]
139
+ else:
140
+ # Two intersections
141
+ sqrt_discriminant = np.sqrt(discriminant)
142
+ x1 = (-B + sqrt_discriminant) / (2 * A)
143
+ y1 = m * x1 + b
144
+ x2 = (-B - sqrt_discriminant) / (2 * A)
145
+ y2 = m * x2 + b
146
+ return [(x1, y1), (x2, y2)]
147
+ else:
148
+ # Vertical line x = c
149
+ c = line
150
+
151
+ # Calculation of the y-values
152
+ A = 1
153
+ B = -2 * center_y
154
+ C = center_y ** 2 + (c - center_x) ** 2 - radius ** 2
155
+
156
+ # Calculation of the discriminant
157
+ discriminant = B ** 2 - 4 * A * C
158
+
159
+ if discriminant < 0:
160
+ # No intersection
161
+ return []
162
+ elif discriminant == 0:
163
+ # Single intersection
164
+ y = -B / (2 * A)
165
+ return [(c, y)]
166
+ else:
167
+ # Two intersections
168
+ sqrt_discriminant = np.sqrt(discriminant)
169
+ y1 = (-B + sqrt_discriminant) / (2 * A)
170
+ y2 = (-B - sqrt_discriminant) / (2 * A)
171
+ return [(c, y1), (c, y2)]