1use super::complex_type::Complex;
6use super::functions::*;
7
8#[derive(Clone, Copy, Debug)]
10pub struct MobiusTransform {
11 pub a: Complex,
13 pub b: Complex,
15 pub c: Complex,
17 pub d: Complex,
19}
20impl MobiusTransform {
21 pub fn new(a: Complex, b: Complex, c: Complex, d: Complex) -> Option<Self> {
23 let det = a.mul(d).sub(b.mul(c));
24 if det.abs() < f64::EPSILON {
25 return None;
26 }
27 Some(Self { a, b, c, d })
28 }
29 pub fn identity() -> Self {
31 Self {
32 a: Complex::one(),
33 b: Complex::zero(),
34 c: Complex::zero(),
35 d: Complex::one(),
36 }
37 }
38 pub fn apply(self, z: Complex) -> Option<Complex> {
40 let numer = self.a.mul(z).add(self.b);
41 let denom = self.c.mul(z).add(self.d);
42 numer.div(denom)
43 }
44 pub fn compose(self, other: Self) -> Option<Self> {
46 let a = self.a.mul(other.a).add(self.b.mul(other.c));
47 let b = self.a.mul(other.b).add(self.b.mul(other.d));
48 let c = self.c.mul(other.a).add(self.d.mul(other.c));
49 let d = self.c.mul(other.b).add(self.d.mul(other.d));
50 Self::new(a, b, c, d)
51 }
52 pub fn invert(self) -> Option<Self> {
54 Self::new(self.d, self.b.neg(), self.c.neg(), self.a)
55 }
56 pub fn fixed_points(self) -> Vec<Complex> {
59 if self.c.abs() < f64::EPSILON {
60 let da = self.d.sub(self.a);
61 if da.abs() < f64::EPSILON {
62 return vec![];
63 }
64 return self.b.div(da).map(|z| vec![z]).unwrap_or_default();
65 }
66 let p = self.d.sub(self.a);
67 let disc = p.mul(p).add(self.b.mul(self.c).scale(4.0));
68 let disc_sqrt = disc.sqrt();
69 let two_c = self.c.scale(2.0);
70 let mut pts = vec![];
71 if let Some(z1) = p.neg().add(disc_sqrt).div(two_c) {
72 pts.push(z1);
73 }
74 if let Some(z2) = p.neg().sub(disc_sqrt).div(two_c) {
75 if pts.is_empty() || !z2.approx_eq(pts[0], 1e-12) {
76 pts.push(z2);
77 }
78 }
79 pts
80 }
81}
82#[allow(dead_code)]
84#[derive(Debug, Clone)]
85pub struct ConformalMap {
86 pub source_domain: String,
87 pub target_domain: String,
88 pub description: String,
89}
90#[allow(dead_code)]
91impl ConformalMap {
92 pub fn new(source: &str, target: &str, desc: &str) -> Self {
93 ConformalMap {
94 source_domain: source.to_string(),
95 target_domain: target.to_string(),
96 description: desc.to_string(),
97 }
98 }
99 pub fn riemann_mapping(domain: &str) -> Self {
100 ConformalMap::new(domain, "UnitDisk", "Riemann mapping theorem")
101 }
102 pub fn upper_half_to_disk() -> Self {
103 ConformalMap::new("UpperHalfPlane", "UnitDisk", "z → (z-i)/(z+i)")
104 }
105 pub fn joukowski() -> Self {
106 ConformalMap::new("UnitDisk", "Airfoil", "z → z + 1/z")
107 }
108 pub fn is_biholomorphic(&self) -> bool {
109 true
110 }
111}
112#[allow(dead_code)]
114#[derive(Debug, Clone, Copy)]
115pub struct MobiusMap {
116 pub a: C64,
117 pub b: C64,
118 pub c: C64,
119 pub d: C64,
120}
121#[allow(dead_code)]
122impl MobiusMap {
123 pub fn new(a: C64, b: C64, c: C64, d: C64) -> Self {
124 MobiusMap { a, b, c, d }
125 }
126 pub fn identity() -> Self {
127 MobiusMap {
128 a: C64::one(),
129 b: C64::zero(),
130 c: C64::zero(),
131 d: C64::one(),
132 }
133 }
134 pub fn determinant(&self) -> C64 {
135 self.a.mul(&self.d).sub(&self.b.mul(&self.c))
136 }
137 pub fn apply(&self, z: C64) -> Option<C64> {
138 let num = self.a.mul(&z).add(&self.b);
139 let den = self.c.mul(&z).add(&self.d);
140 num.div(&den)
141 }
142 pub fn compose(&self, other: &MobiusMap) -> MobiusMap {
143 MobiusMap {
144 a: self.a.mul(&other.a).add(&self.b.mul(&other.c)),
145 b: self.a.mul(&other.b).add(&self.b.mul(&other.d)),
146 c: self.c.mul(&other.a).add(&self.d.mul(&other.c)),
147 d: self.c.mul(&other.b).add(&self.d.mul(&other.d)),
148 }
149 }
150 pub fn is_parabolic(&self) -> bool {
151 let tr = self.a.add(&self.d);
152 let tr2 = tr.mul(&tr);
153 (tr2.re - 4.0).abs() < 1e-9 && tr2.im.abs() < 1e-9
154 }
155}
156#[allow(dead_code)]
158#[derive(Debug, Clone, Copy, PartialEq)]
159pub struct C64 {
160 pub re: f64,
161 pub im: f64,
162}
163#[allow(dead_code)]
164impl C64 {
165 pub fn new(re: f64, im: f64) -> Self {
166 C64 { re, im }
167 }
168 pub fn zero() -> Self {
169 C64::new(0.0, 0.0)
170 }
171 pub fn one() -> Self {
172 C64::new(1.0, 0.0)
173 }
174 pub fn i() -> Self {
175 C64::new(0.0, 1.0)
176 }
177 pub fn from_polar(r: f64, theta: f64) -> Self {
178 C64::new(r * theta.cos(), r * theta.sin())
179 }
180 pub fn modulus(&self) -> f64 {
181 (self.re * self.re + self.im * self.im).sqrt()
182 }
183 pub fn argument(&self) -> f64 {
184 self.im.atan2(self.re)
185 }
186 pub fn conjugate(&self) -> Self {
187 C64::new(self.re, -self.im)
188 }
189 pub fn add(&self, other: &C64) -> C64 {
190 C64::new(self.re + other.re, self.im + other.im)
191 }
192 pub fn sub(&self, other: &C64) -> C64 {
193 C64::new(self.re - other.re, self.im - other.im)
194 }
195 pub fn mul(&self, other: &C64) -> C64 {
196 C64::new(
197 self.re * other.re - self.im * other.im,
198 self.re * other.im + self.im * other.re,
199 )
200 }
201 pub fn div(&self, other: &C64) -> Option<C64> {
202 let denom = other.re * other.re + other.im * other.im;
203 if denom.abs() < 1e-15 {
204 return None;
205 }
206 Some(C64::new(
207 (self.re * other.re + self.im * other.im) / denom,
208 (self.im * other.re - self.re * other.im) / denom,
209 ))
210 }
211 pub fn exp(&self) -> C64 {
212 let r = self.re.exp();
213 C64::new(r * self.im.cos(), r * self.im.sin())
214 }
215 pub fn log(&self) -> Option<C64> {
216 let r = self.modulus();
217 if r < 1e-15 {
218 return None;
219 }
220 Some(C64::new(r.ln(), self.argument()))
221 }
222 pub fn pow_n(&self, n: i32) -> C64 {
223 let r = self.modulus().powi(n);
224 let theta = self.argument() * n as f64;
225 C64::from_polar(r, theta)
226 }
227 pub fn sqrt_principal(&self) -> C64 {
228 let r = self.modulus().sqrt();
229 let theta = self.argument() / 2.0;
230 C64::from_polar(r, theta)
231 }
232 pub fn sin(&self) -> C64 {
233 C64::new(
234 self.re.sin() * self.im.cosh(),
235 self.re.cos() * self.im.sinh(),
236 )
237 }
238 pub fn cos(&self) -> C64 {
239 C64::new(
240 self.re.cos() * self.im.cosh(),
241 -self.re.sin() * self.im.sinh(),
242 )
243 }
244}