Integration estimation

Needs more a bit more accuracy though. Currently the n value in the
trapezoidal rule is fixed.
This commit is contained in:
bakk 2021-05-16 15:15:34 +02:00
parent 439f732013
commit 651b289f4b
6 changed files with 116 additions and 0 deletions

View File

@ -302,6 +302,79 @@ fn eval_fn_call_expr(
return Ok(sum);
}
"integrate" | "" => {
// Make sure exactly 3 arguments were supplied.
if expressions.len() != 3 {
return Err(CalcError::IncorrectAmountOfArguments(
3,
"integrate".into(),
expressions.len(),
));
}
let mut result = KalkNum::default();
let mut integration_variable: Option<&str> = None;
// integral(a, b, expr dx)
if let Expr::Binary(_, TokenKind::Star, right) = &expressions[2] {
if let Expr::Var(right_name) = &**right {
if right_name.starts_with("d") {
// Take the value, but remove the d, so that only eg. x is left from dx
integration_variable = Some(&right_name[1..]);
}
}
}
if integration_variable.is_none() {
unimplemented!(); // TODO: Error message
}
// delta_x/2[f(a) + 2f(x_1) + 2f(x_2) + ...2f(x_n) + f(b)]
// where delta_x = (b - a) / n
// and x_n = a + i * delta_x
// f(a)
context.symbol_table.set(Stmt::VarDecl(
integration_variable.unwrap().into(),
Box::new(expressions[0].clone()),
));
// "dx" is still in the expression. Set dx = 1, so that it doesn't affect the expression value.
context.symbol_table.set(Stmt::VarDecl(
String::from("dx"),
Box::new(Expr::Literal(1f64)),
));
result.value += eval_expr(context, &expressions[2], "")?.value;
// 2f(x_n)
// where x_n = a + i * delta_x
const N: i32 = 100;
let a = eval_expr(context, &expressions[0], "")?.value.to_f64();
let b = eval_expr(context, &expressions[1], "")?.value.to_f64();
let delta_x = (b - a) / N as f64;
for i in 1..N {
context.symbol_table.set(Stmt::VarDecl(
integration_variable.unwrap().into(),
Box::new(Expr::Literal(a + i as f64 * delta_x)),
));
// 2f(x_n)
result.value += 2 * eval_expr(context, &expressions[2], "")?.value;
}
// f(b)
context.symbol_table.set(Stmt::VarDecl(
integration_variable.unwrap().into(),
Box::new(expressions[1].clone()),
));
result.value += eval_expr(context, &expressions[2], "")?.value;
// Finally, delta_x/2 for all of it
result.value *= delta_x / 2f64;
return Ok(result);
}
_ => (),
}
@ -550,4 +623,22 @@ mod tests {
assert_eq!(interpret(stmt).unwrap().unwrap().to_f64(), result);
}
#[test]
fn test_integrate_fn() {
let stmt = Stmt::Expr(fn_call(
"integrate",
vec![
*literal(2f64),
*literal(4f64),
*binary(
binary(var("x"), TokenKind::Power, literal(3f64)),
TokenKind::Star,
binary(var("d"), TokenKind::Star, var("x")),
),
],
));
assert!((interpret(stmt).unwrap().unwrap().to_f64() - 60f64).abs() < 1f64);
}
}

View File

@ -29,6 +29,7 @@ pub struct Context {
parsing_identifier_stmt: bool,
equation_variable: Option<String>,
contains_equal_sign: bool,
is_in_integral: bool,
}
#[wasm_bindgen]
@ -46,6 +47,7 @@ impl Context {
parsing_identifier_stmt: false,
equation_variable: None,
contains_equal_sign: false,
is_in_integral: false,
};
parse(&mut context, crate::prelude::INIT).unwrap();
@ -491,6 +493,10 @@ fn parse_identifier(context: &mut Context) -> Result<Expr, CalcError> {
if !parse_as_var_instead && match_token(context, TokenKind::OpenParenthesis) {
advance(context);
if identifier.value == "integrate" || identifier.value == "" {
context.is_in_integral = true;
}
let mut parameters = Vec::new();
parameters.push(parse_expr(context)?);
@ -500,10 +506,16 @@ fn parse_identifier(context: &mut Context) -> Result<Expr, CalcError> {
}
consume(context, TokenKind::ClosedParenthesis)?;
context.is_in_integral = false;
return Ok(Expr::FnCall(identifier.value, parameters));
}
// Eg. dx inside an integral, should be parsed as *one* identifier
if context.is_in_integral && identifier.value.starts_with("d") {
return Ok(Expr::Var(identifier.value));
}
// Eg. x
if parse_as_var_instead || context.symbol_table.contains_var(&identifier.value) {
Ok(Expr::Var(identifier.value))

View File

@ -35,6 +35,8 @@ impl BinaryFuncInfo {
pub fn is_prelude_func(identifier: &str) -> bool {
identifier == "sum"
|| identifier == "Σ"
|| identifier == "integrate"
|| identifier == ""
|| UNARY_FUNCS.contains_key(identifier)
|| BINARY_FUNCS.contains_key(identifier)
}

View File

@ -42,6 +42,8 @@ impl BinaryFuncInfo {
pub fn is_prelude_func(identifier: &str) -> bool {
identifier == "sum"
|| identifier == "Σ"
|| identifier == "integrate"
|| identifier == ""
|| UNARY_FUNCS.contains_key(identifier)
|| BINARY_FUNCS.contains_key(identifier)
}

View File

@ -111,6 +111,7 @@ lazy_static! {
m.insert("floor", "⌊⌋");
m.insert("gamma", "Γ");
m.insert("sum", "Σ()");
m.insert("integrate", "∫()");
m.insert("phi", "ϕ");
m.insert("pi", "π");
m.insert("sqrt", "");
@ -143,6 +144,7 @@ impl Completer for RLHelper {
line.insert_str(line.pos(), elected);
line.move_forward(match elected {
"Σ()" => 2,
"∫()" => 2,
_ => 1,
});
}

View File

@ -177,6 +177,9 @@
} else if (input == "Σ") {
input += "()";
movementOffset = 2;
} else if (input == "∫") {
input += "()";
movementOffset = 2;
} else if (input == "⌊") {
input += "⌋";
} else if (input == "⌈") {
@ -283,6 +286,10 @@
newSubstring = "Σ";
break;
}
case "integrate": {
newSubstring = "∫";
break;
}
case "pi": {
newSubstring = "π";
break;