1use sim_kernel::{Cx, Expr, Linker, NumberLiteral, PromotionRule, Result, Value};
5use sim_lib_numbers_core::domains;
6
7use super::literal::{f64_domain, i64_domain, number_domain, rational_domain};
8
9pub(super) type ComplexRuleFn = fn(&mut Cx, NumberLiteral, NumberLiteral) -> Result<Value>;
10pub(super) type ValueRuleFn = fn(&mut Cx, Value, Value) -> Result<Value>;
11
12pub fn register_promotions(linker: &mut Linker<'_>) {
13 for rule in [
14 PromotionRule {
15 from_domain: f64_domain(),
16 to_domain: number_domain(),
17 cost: 1,
18 convert: promote_f64_to_complex,
19 },
20 PromotionRule {
21 from_domain: i64_domain(),
22 to_domain: number_domain(),
23 cost: 1,
24 convert: promote_i64_to_complex,
25 },
26 PromotionRule {
27 from_domain: rational_domain(),
28 to_domain: number_domain(),
29 cost: 1,
30 convert: promote_rational_to_complex,
31 },
32 ] {
33 linker.promotion_rule(rule);
34 }
35}
36
37pub(super) fn complex_add_rule(
38 cx: &mut Cx,
39 left: NumberLiteral,
40 right: NumberLiteral,
41) -> Result<Value> {
42 complex_rule(
43 cx,
44 left,
45 right,
46 |left, right| Some((left.0 + right.0, left.1 + right.1)),
47 "add",
48 )
49}
50
51pub(super) fn complex_sub_rule(
52 cx: &mut Cx,
53 left: NumberLiteral,
54 right: NumberLiteral,
55) -> Result<Value> {
56 complex_rule(
57 cx,
58 left,
59 right,
60 |left, right| Some((left.0 - right.0, left.1 - right.1)),
61 "sub",
62 )
63}
64
65pub(super) fn complex_mul_rule(
66 cx: &mut Cx,
67 left: NumberLiteral,
68 right: NumberLiteral,
69) -> Result<Value> {
70 complex_rule(
71 cx,
72 left,
73 right,
74 |left, right| {
75 Some((
76 left.0 * right.0 - left.1 * right.1,
77 left.0 * right.1 + left.1 * right.0,
78 ))
79 },
80 "mul",
81 )
82}
83
84pub(super) fn complex_div_rule(
85 cx: &mut Cx,
86 left: NumberLiteral,
87 right: NumberLiteral,
88) -> Result<Value> {
89 complex_rule(
90 cx,
91 left,
92 right,
93 |left, right| {
94 let denominator = right.0 * right.0 + right.1 * right.1;
95 if denominator == 0.0 {
96 return None;
97 }
98 Some((
99 (left.0 * right.0 + left.1 * right.1) / denominator,
100 (left.1 * right.0 - left.0 * right.1) / denominator,
101 ))
102 },
103 "div",
104 )
105}
106
107pub(super) fn complex_neg_rule(cx: &mut Cx, operand: NumberLiteral) -> Result<Value> {
108 let operand = parse_complex_number(operand, "operand")?;
109 cx.factory()
110 .number_literal(number_domain(), canonical_complex(-operand.0, -operand.1))
111}
112
113pub(super) fn complex_sum_rule(cx: &mut Cx, operands: Vec<NumberLiteral>) -> Result<Value> {
114 let mut acc = (0.0_f64, 0.0_f64);
115 for operand in operands {
116 let operand = parse_complex_number(operand, "operand")?;
117 acc = (acc.0 + operand.0, acc.1 + operand.1);
118 }
119 cx.factory()
120 .number_literal(number_domain(), canonical_complex(acc.0, acc.1))
121}
122
123pub(super) fn complex_product_rule(cx: &mut Cx, operands: Vec<NumberLiteral>) -> Result<Value> {
124 let mut acc = (1.0_f64, 0.0_f64);
125 for operand in operands {
126 let operand = parse_complex_number(operand, "operand")?;
127 acc = (
128 acc.0 * operand.0 - acc.1 * operand.1,
129 acc.0 * operand.1 + acc.1 * operand.0,
130 );
131 }
132 cx.factory()
133 .number_literal(number_domain(), canonical_complex(acc.0, acc.1))
134}
135
136pub(crate) fn complex_add_value_rule(cx: &mut Cx, left: Value, right: Value) -> Result<Value> {
137 let left = expect_domain_literal(cx, left, "left")?;
138 let right = expect_domain_literal(cx, right, "right")?;
139 complex_add_rule(cx, left, right)
140}
141
142pub(crate) fn complex_sub_value_rule(cx: &mut Cx, left: Value, right: Value) -> Result<Value> {
143 let left = expect_domain_literal(cx, left, "left")?;
144 let right = expect_domain_literal(cx, right, "right")?;
145 complex_sub_rule(cx, left, right)
146}
147
148pub(crate) fn complex_mul_value_rule(cx: &mut Cx, left: Value, right: Value) -> Result<Value> {
149 let left = expect_domain_literal(cx, left, "left")?;
150 let right = expect_domain_literal(cx, right, "right")?;
151 complex_mul_rule(cx, left, right)
152}
153
154pub(crate) fn complex_div_value_rule(cx: &mut Cx, left: Value, right: Value) -> Result<Value> {
155 let left = expect_domain_literal(cx, left, "left")?;
156 let right = expect_domain_literal(cx, right, "right")?;
157 complex_div_rule(cx, left, right)
158}
159
160pub(crate) fn complex_neg_value_rule(cx: &mut Cx, operand: Value) -> Result<Value> {
161 let operand = expect_domain_literal(cx, operand, "operand")?;
162 complex_neg_rule(cx, operand)
163}
164
165pub(crate) fn complex_sum_value_rule(cx: &mut Cx, operands: Vec<Value>) -> Result<Value> {
166 let operands = expect_domain_literals(cx, operands, "operand")?;
167 complex_sum_rule(cx, operands)
168}
169
170pub(crate) fn complex_product_value_rule(cx: &mut Cx, operands: Vec<Value>) -> Result<Value> {
171 let operands = expect_domain_literals(cx, operands, "operand")?;
172 complex_product_rule(cx, operands)
173}
174
175fn complex_rule(
176 cx: &mut Cx,
177 left: NumberLiteral,
178 right: NumberLiteral,
179 apply: impl FnOnce((f64, f64), (f64, f64)) -> Option<(f64, f64)>,
180 name: &str,
181) -> Result<Value> {
182 let left = parse_complex_number(left, "left")?;
183 let right = parse_complex_number(right, "right")?;
184 let out = apply(left, right).ok_or_else(|| {
185 sim_kernel::Error::Eval(format!(
186 "{name} failed in complex arithmetic (overflow or divide by zero)"
187 ))
188 })?;
189 cx.factory()
190 .number_literal(number_domain(), canonical_complex(out.0, out.1))
191}
192
193fn parse_complex_number(number: NumberLiteral, side: &str) -> Result<(f64, f64)> {
194 if number.domain != number_domain() {
195 return Err(sim_kernel::Error::Eval(format!(
196 "{side} operand expected number domain {}, found {}",
197 number_domain(),
198 number.domain
199 )));
200 }
201 parse_complex_literal(&number.canonical).ok_or_else(|| {
202 sim_kernel::Error::Eval(format!(
203 "{side} operand was not a valid complex literal: {}",
204 number.canonical
205 ))
206 })
207}
208
209pub fn parse_complex_literal(text: &str) -> Option<(f64, f64)> {
222 let trimmed = text.trim();
223 let imag_text = trimmed.strip_suffix('i')?;
224 let split = imag_text
225 .as_bytes()
226 .get(1..)?
227 .iter()
228 .rposition(|byte| *byte == b'+' || *byte == b'-')
229 .map(|index| index + 1)?;
230 let (real_text, imag_with_sign) = imag_text.split_at(split);
231 let real = real_text.parse::<f64>().ok()?;
232 let imag = imag_with_sign.parse::<f64>().ok()?;
233 Some((real, imag))
234}
235
236pub fn canonical_complex(real: f64, imag: f64) -> String {
248 let real = canonical_f64(real);
249 let imag = canonical_f64(imag);
250 if imag.starts_with('-') {
251 format!("{real}{imag}i")
252 } else {
253 format!("{real}+{imag}i")
254 }
255}
256
257pub fn canonical_f64(value: f64) -> String {
260 let rendered = value.to_string();
261 if rendered == "-0" {
262 "0".to_owned()
263 } else {
264 rendered
265 }
266}
267
268fn promote_f64_to_complex(_cx: &mut Cx, number: NumberLiteral) -> Result<NumberLiteral> {
269 if number.domain != f64_domain() {
270 return Err(sim_kernel::Error::Eval(format!(
271 "expected number domain {} for promotion, found {}",
272 f64_domain(),
273 number.domain
274 )));
275 }
276 let value = number.canonical.parse::<f64>().map_err(|err| {
277 sim_kernel::Error::Eval(format!("could not promote f64 literal to complex: {}", err))
278 })?;
279 Ok(NumberLiteral {
280 domain: number_domain(),
281 canonical: canonical_complex(value, 0.0),
282 })
283}
284
285fn promote_i64_to_complex(_cx: &mut Cx, number: NumberLiteral) -> Result<NumberLiteral> {
286 if number.domain != i64_domain() {
287 return Err(sim_kernel::Error::Eval(format!(
288 "expected number domain {} for promotion, found {}",
289 i64_domain(),
290 number.domain
291 )));
292 }
293 let value = number.canonical.parse::<i64>().map_err(|err| {
294 sim_kernel::Error::Eval(format!("could not promote i64 literal to complex: {}", err))
295 })?;
296 Ok(NumberLiteral {
297 domain: number_domain(),
298 canonical: canonical_complex(value as f64, 0.0),
299 })
300}
301
302fn promote_rational_to_complex(_cx: &mut Cx, number: NumberLiteral) -> Result<NumberLiteral> {
303 if number.domain != rational_domain() {
304 return Err(sim_kernel::Error::Eval(format!(
305 "expected number domain {} for promotion, found {}",
306 rational_domain(),
307 number.domain
308 )));
309 }
310 let value = parse_rational_as_f64(&number.canonical).ok_or_else(|| {
311 sim_kernel::Error::Eval(format!(
312 "could not promote rational literal to complex: {}",
313 number.canonical
314 ))
315 })?;
316 Ok(NumberLiteral {
317 domain: number_domain(),
318 canonical: canonical_complex(value, 0.0),
319 })
320}
321
322pub(crate) fn promote_f64_value_to_complex(cx: &mut Cx, value: Value) -> Result<Value> {
323 let number = expect_number_literal(cx, value, f64_domain(), "operand")?;
324 let promoted = promote_f64_to_complex(cx, number)?;
325 cx.factory()
326 .number_literal(promoted.domain, promoted.canonical)
327}
328
329pub(crate) fn promote_i64_value_to_complex(cx: &mut Cx, value: Value) -> Result<Value> {
330 let number = expect_number_literal(cx, value, i64_domain(), "operand")?;
331 let promoted = promote_i64_to_complex(cx, number)?;
332 cx.factory()
333 .number_literal(promoted.domain, promoted.canonical)
334}
335
336pub(crate) fn promote_rational_value_to_complex(cx: &mut Cx, value: Value) -> Result<Value> {
337 let Some(number) = cx.number_value_ref(value.clone())? else {
338 return Err(sim_kernel::Error::Eval(format!(
339 "operand expected number domain {}, found non-number",
340 rational_domain()
341 )));
342 };
343 if number.domain != rational_domain() {
344 return Err(sim_kernel::Error::Eval(format!(
345 "operand expected number domain {}, found {}",
346 rational_domain(),
347 number.domain
348 )));
349 }
350 if let Some(literal) = number.literal {
351 let promoted = promote_rational_to_complex(cx, literal)?;
352 return cx
353 .factory()
354 .number_literal(promoted.domain, promoted.canonical);
355 }
356
357 let value = noncompact_rational_as_f64(cx, value)?;
358 cx.factory()
359 .number_literal(number_domain(), canonical_complex(value, 0.0))
360}
361
362pub fn parse_rational_as_f64(text: &str) -> Option<f64> {
365 let (numerator, denominator) = text.split_once('/')?;
366 let numerator = numerator.parse::<f64>().ok()?;
367 let denominator = denominator.parse::<f64>().ok()?;
368 if denominator == 0.0 {
369 return None;
370 }
371 Some(numerator / denominator)
372}
373
374fn expect_domain_literal(cx: &mut Cx, value: Value, side: &str) -> Result<NumberLiteral> {
375 expect_number_literal(cx, value, number_domain(), side)
376}
377
378fn expect_number_literal(
379 cx: &mut Cx,
380 value: Value,
381 expected_domain: sim_kernel::Symbol,
382 side: &str,
383) -> Result<NumberLiteral> {
384 let Some(number) = cx.number_value_ref(value)? else {
385 return Err(sim_kernel::Error::Eval(format!(
386 "{side} operand expected number domain {}, found non-number",
387 expected_domain
388 )));
389 };
390 if number.domain != expected_domain {
391 return Err(sim_kernel::Error::Eval(format!(
392 "{side} operand expected number domain {}, found {}",
393 expected_domain, number.domain
394 )));
395 }
396 number.literal.ok_or_else(|| {
397 sim_kernel::Error::Eval(format!(
398 "{side} operand in {} does not have a canonical literal form",
399 expected_domain
400 ))
401 })
402}
403
404fn expect_domain_literals(
405 cx: &mut Cx,
406 values: Vec<Value>,
407 side: &str,
408) -> Result<Vec<NumberLiteral>> {
409 values
410 .into_iter()
411 .map(|value| expect_domain_literal(cx, value, side))
412 .collect()
413}
414
415fn noncompact_rational_as_f64(cx: &mut Cx, value: Value) -> Result<f64> {
416 let Expr::Extension { tag, payload } = value.object().as_expr(cx)? else {
417 return Err(sim_kernel::Error::Eval(
418 "operand rational value does not have a complex-compatible surface".to_owned(),
419 ));
420 };
421 if tag != domains::rational_value_class() {
422 return Err(sim_kernel::Error::Eval(format!(
423 "operand expected rational value extension, found {}",
424 tag
425 )));
426 }
427 let Expr::Vector(parts) = payload.as_ref() else {
428 return Err(sim_kernel::Error::Eval(
429 "operand rational extension payload must be a vector".to_owned(),
430 ));
431 };
432 let [num, den] = parts.as_slice() else {
433 return Err(sim_kernel::Error::Eval(
434 "operand rational extension must contain numerator and denominator".to_owned(),
435 ));
436 };
437 let num = number_expr_as_f64(num, "numerator")?;
438 let den = number_expr_as_f64(den, "denominator")?;
439 if den == 0.0 {
440 return Err(sim_kernel::Error::Eval(
441 "operand rational denominator must not be zero".to_owned(),
442 ));
443 }
444 Ok(num / den)
445}
446
447fn number_expr_as_f64(expr: &Expr, side: &str) -> Result<f64> {
448 let Expr::Number(number) = expr else {
449 return Err(sim_kernel::Error::Eval(format!(
450 "{side} expected numeric literal in rational extension"
451 )));
452 };
453 number.canonical.parse::<f64>().map_err(|err| {
454 sim_kernel::Error::Eval(format!(
455 "{side} could not be converted to f64 for complex promotion: {}",
456 err
457 ))
458 })
459}