C# code for symbolic differentiation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// See https://www.pixata.co.uk/2023/10/10/symbolic-differentiation-in-c/ for an explanation | |
// The code below can be pasted as-is into LinqPad (change the language to "C# statements"), or used in a console application in Visual studio. | |
DifExp de1 = new(new(1), "*", new(2)); | |
Console.WriteLine($"{de1} -> {de1.Differentiate("x")}"); | |
DifExp de2 = new(new("x"), "+", de1); | |
Console.WriteLine($"{de2} -> {de2.Differentiate("x")}"); | |
DifExp de3 = new(new(2), "*", new("x")); | |
Console.WriteLine($"{de3} -> {de3.Differentiate("x")}"); | |
DifExp de4 = new(de3, "+", new(10)); | |
Console.WriteLine($"{de4} -> {de4.Differentiate("x")}"); | |
DifExp de5 = new(new("x"), "*", new("x")); // Don't so this, use the power ctor instead. Same with the next one | |
Console.WriteLine($"{de5} -> {de5.Differentiate("x")}"); | |
DifExp de6 = new(new("x"), "*", new(new("x"), "*", new("x"))); | |
Console.WriteLine($"{de6} -> {de6.Differentiate("x")}"); | |
DifExp de7 = new(new("x"), 2); | |
Console.WriteLine($"{de7} -> {de7.Differentiate("x")}"); | |
DifExp de8 = new(new("x"), 4); | |
Console.WriteLine($"{de8} -> {de8.Differentiate("x")}"); | |
class DifExp { | |
// This isn't so neat, as there is nothing to stop someone accessing the Expression property of a value, etc | |
private DifExpType Type { init; get; } | |
private int Value { init; get; } | |
private string Symbol { init; get; } = ""; | |
private (DifExp E1, string Op, DifExp E2) Expression { init; get; } | |
public DifExp(int value) { | |
Type = DifExpType.Value; | |
Value = value; | |
} | |
public DifExp(string symbol) { | |
Type = DifExpType.Symbol; | |
Symbol = symbol; | |
} | |
public DifExp(string symbol, int power) { | |
// This ctor is used for raising a symbol to an integer power | |
Type = DifExpType.Expresssion; | |
Expression = (new(symbol), "^", new(power)); | |
} | |
public DifExp(DifExp e1, string op, DifExp e2, bool simplify = true) { | |
if (!new[] { "+", "*", "-", "^" }.Contains(op)) { | |
throw new ArgumentException($"Unsupported operator: {op}"); | |
} | |
//Console.WriteLine($"Ctor({e1}, {op}, {e2})"); | |
if (simplify) { | |
//Console.WriteLine("ctor - calling Simplify()"); | |
DifExp exp = Simplify(e1, op, e2); | |
Type = exp.Type; | |
Value = exp.Value; | |
Symbol = exp.Symbol; | |
Expression = exp.Expression; | |
} else { | |
//Console.WriteLine("ctor - not calling Simplify()"); | |
Type = DifExpType.Expresssion; | |
Expression = (e1, op, e2); | |
} | |
} | |
public static DifExp Simplify(DifExp e) { | |
if (e.Type != DifExpType.Expresssion) { | |
return e; | |
} | |
return Simplify(e.Expression.E1, e.Expression.Op, e.Expression.E2); | |
} | |
public static DifExp Simplify(DifExp e1, string op, DifExp e2) { | |
DifExp e1s = Simplify(e1); | |
DifExp e2s = Simplify(e2); | |
if (e1s.Type == DifExpType.Value && e2s.Type == DifExpType.Value) { | |
// Both expressions are values, so combine them wth the operator specified | |
return new(op switch { | |
"+" => e1s.Value + e2s.Value, | |
"*" => e1s.Value * e2s.Value, | |
"-" => e1s.Value - e2s.Value | |
}); | |
} else if (op == "*" && (e1s.Type == DifExpType.Value && e1s.Value == 0) || (e2s.Type == DifExpType.Value && e2s.Value == 0)) { | |
// We are multiplying, and one expression is a value of zero, then we are a value of zero | |
return new(0); | |
} else if (op == "*" && e1s.Type == DifExpType.Value && e1s.Value == 1) { | |
// We are multiplying, and the first expression is a value of one, then we are the other expression | |
return e2s; | |
} else if (op == "*" && e2s.Type == DifExpType.Value && e2s.Value == 1) { | |
// Ditto the previous comment for the second expression | |
return e1s; | |
} else if (op == "+" && e1s.Type == DifExpType.Value && e1s.Value == 0) { | |
// We are adding, and the first expression is a value of zero, then we are the other expression | |
return e2s; | |
} else if (op == "+" && e2s.Type == DifExpType.Value && e2s.Value == 0) { | |
// Ditto the previous comment for the second expression | |
return e1s; | |
} | |
// Nothing to simplify, so return a new expression | |
return new(e1s, op, e2s, false); | |
} | |
public DifExp Differentiate(string symbol) => | |
Type switch { | |
DifExpType.Value => new(0), | |
DifExpType.Symbol => symbol == Symbol ? new(1) : new(Symbol), | |
DifExpType.Expresssion => | |
Expression.Op switch { | |
"+" => new(Expression.E1.Differentiate(symbol), "+", Expression.E2.Differentiate(symbol)), | |
"-" => new(Expression.E1.Differentiate(symbol), "-", Expression.E2.Differentiate(symbol)), | |
"*" => new(new(Expression.E1.Differentiate(symbol), "*", Expression.E2), "+", new(Expression.E1, "*", Expression.E2.Differentiate(symbol))), | |
"^" => new(new(Expression.E2.Value), "*", new(new(symbol), "^", new(Expression.E2.Value - 1))) | |
} | |
}; | |
public override string ToString() => | |
Type switch { | |
DifExpType.Value => Value.ToString(), | |
DifExpType.Symbol => Symbol, | |
DifExpType.Expresssion => $"({Expression.E1} {Expression.Op} {Expression.E2})" | |
}; | |
public enum DifExpType { | |
Value, | |
Symbol, | |
Expresssion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment