Skip to content

Commit

Permalink
Merge pull request #40 from SpiceSharp/development
Browse files Browse the repository at this point in the history
v3.0.4
  • Loading branch information
svenboulanger authored Jan 25, 2021
2 parents 2c0c4d3 + 5671d59 commit ff07aed
Show file tree
Hide file tree
Showing 65 changed files with 4,383 additions and 1,013 deletions.
118 changes: 118 additions & 0 deletions SpiceSharpBehavioral/Builders/Direct/ComplexBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
using SpiceSharp;
using SpiceSharp.Simulations;
using SpiceSharpBehavioral.Parsers.Nodes;
using System;
using System.Collections.Generic;
using System.Numerics;

namespace SpiceSharpBehavioral.Builders.Direct
{
/// <summary>
/// A builder that can compute values.
/// </summary>
/// <seealso cref="IBuilder{T}" />
public class ComplexBuilder : IDirectBuilder<Complex>
{
/// <inheritdoc/>
public event EventHandler<FunctionFoundEventArgs<Complex>> FunctionFound;

/// <inheritdoc/>
public event EventHandler<VariableFoundEventArgs<Complex>> VariableFound;

/// <inheritdoc/>
public double FudgeFactor { get; set; } = 1e-20;

/// <inheritdoc/>
public double RelativeTolerance { get; set; } = 1e-6;

/// <inheritdoc/>
public double AbsoluteTolerance { get; set; } = 1e-12;

/// <summary>
/// Builds the specified value from the specified expression node.
/// </summary>
/// <param name="expression">The expression node.</param>
/// <returns>
/// The value.
/// </returns>
public Complex Build(Node expression)
{
switch (expression)
{
case BinaryOperatorNode bn:
switch (bn.NodeType)
{
case NodeTypes.Add: return Build(bn.Left) + Build(bn.Right);
case NodeTypes.Subtract: return Build(bn.Left) - Build(bn.Right);
case NodeTypes.Multiply: return Build(bn.Left) * Build(bn.Right);
case NodeTypes.Divide: return HelperFunctions.SafeDivide(Build(bn.Left), Build(bn.Right), FudgeFactor);
case NodeTypes.Modulo: return Build(bn.Left).Real % Build(bn.Right).Real;
case NodeTypes.LessThan: return Build(bn.Left).Real < Build(bn.Right).Real ? 1.0 : 0.0;
case NodeTypes.GreaterThan: return Build(bn.Left).Real > Build(bn.Right).Real ? 1.0 : 0.0;
case NodeTypes.LessThanOrEqual: return Build(bn.Left).Real <= Build(bn.Right).Real ? 1.0 : 0.0;
case NodeTypes.GreaterThanOrEqual: return Build(bn.Left).Real >= Build(bn.Right).Real ? 1.0 : 0.0;
case NodeTypes.Equals: return HelperFunctions.Equals(Build(bn.Left), Build(bn.Right), RelativeTolerance, AbsoluteTolerance) ? 1.0 : 0.0;
case NodeTypes.NotEquals: return HelperFunctions.Equals(Build(bn.Left), Build(bn.Right), RelativeTolerance, AbsoluteTolerance) ? 0.0 : 1.0;
case NodeTypes.And: return Build(bn.Left).Real > 0.5 && Build(bn.Right).Real > 0.5 ? 1.0 : 0.0;
case NodeTypes.Or: return Build(bn.Left).Real > 0.5 || Build(bn.Right).Real > 0.5 ? 1.0 : 0.0;
case NodeTypes.Xor: return Build(bn.Left).Real > 0.5 ^ Build(bn.Right).Real > 0.5 ? 1.0 : 0.0;
case NodeTypes.Pow: return HelperFunctions.Power(Build(bn.Left), Build(bn.Right));
}
break;

case UnaryOperatorNode un:
switch (un.NodeType)
{
case NodeTypes.Plus: return Build(un.Argument);
case NodeTypes.Minus: return -Build(un.Argument);
case NodeTypes.Not: return Build(un.Argument).Real > 0.5 ? 0.0 : 1.0;
}
break;

case TernaryOperatorNode tn:
return Build(tn.Condition).Real > 0.5 ? Build(tn.IfTrue) : Build(tn.IfFalse);

case FunctionNode fn:
var fargs = new FunctionFoundEventArgs<Complex>(this, fn);
OnFunctionFound(fargs);
if (!fargs.Created)
throw new SpiceSharpException($"Could not recognized function {fn.Name}");
return fargs.Result;

case ConstantNode cn:
return cn.Literal;

case VariableNode vn:
var vargs = new VariableFoundEventArgs<Complex>(this, vn);
OnVariableFound(vargs);
if (!vargs.Created)
throw new SpiceSharpException($"Could not recognized variable {vn.Name}");
return vargs.Result;
}
return BuildNode(expression);
}

/// <summary>
/// Called when a function was found.
/// </summary>
/// <param name="args">The event arguments.</param>
protected virtual void OnFunctionFound(FunctionFoundEventArgs<Complex> args) => FunctionFound?.Invoke(this, args);

/// <summary>
/// Called when a variable was found.
/// </summary>
/// <param name="args">The event arguments.</param>
protected virtual void OnVariableFound(VariableFoundEventArgs<Complex> args) => VariableFound?.Invoke(this, args);

/// <summary>
/// Builds the node.
/// </summary>
/// <param name="node">The node.</param>
/// <returns>The built value.</returns>
/// <exception cref="SpiceSharpException">Unrecognized node</exception>
protected virtual Complex BuildNode(Node node)
{
throw new SpiceSharpException("Unrecognized expression node {0}".FormatString(node));
}
}
}
172 changes: 172 additions & 0 deletions SpiceSharpBehavioral/Builders/Direct/ComplexBuilderHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
using SpiceSharp;
using SpiceSharpBehavioral.Diagnostics;
using System;
using System.Collections.Generic;
using System.Numerics;

namespace SpiceSharpBehavioral.Builders.Direct
{
/// <summary>
/// Helper methods for a <see cref="ComplexBuilder"/>.
/// </summary>
public static class ComplexBuilderHelper
{
private static readonly Random _rnd = new Random();

/// <summary>
/// A set of default functions.
/// </summary>
public static readonly Dictionary<string, Func<Complex[], Complex>> Defaults = new Dictionary<string, Func<Complex[], Complex>>(StringComparer.OrdinalIgnoreCase)
{
{ "abs", Abs },
{ "sgn", Sgn },
{ "sqrt", Sqrt },
{ "pow", Pow },
{ "pwr", Pwr },
{ "pwrs", Pwrs },
{ "log", Log }, { "ln", Log },
{ "log10", Log10 },
{ "exp", Exp },
{ "sin", Sin },
{ "cos", Cos },
{ "tan", Tan },
{ "sinh", Sinh },
{ "cosh", Cosh },
{ "tanh", Tanh },
{ "asin", Asin }, { "arcsin", Asin },
{ "acos", Acos }, { "arccos", Acos },
{ "atan", Atan }, { "arctan", Atan },
{ "u", U },
{ "u2", U2 },
{ "uramp", URamp },
{ "ceil", Ceil },
{ "floor", Floor },
{ "nint", Nint },
{ "round", Round },
{ "square", Square },
{ "pwl", Pwl }, { "dpwl(0)", PwlDerivative },
{ "table", Pwl }, { "dtable(0)", PwlDerivative },
{ "tbl", Pwl }, { "dtbl(0)", PwlDerivative },
{ "min", Min },
{ "max", Max },
{ "atan2", Atan2 },
{ "hypot", Hypot },
{ "rnd", Random }, { "rand", Random },
{ "if", If }
};

private static Complex[] Check(this Complex[] args, int expected)
{
if (args == null || args.Length != expected)
throw new ArgumentMismatchException(expected, args?.Length ?? 0);
return args;
}

/// <summary>
/// Registers the default functions.
/// </summary>
/// <param name="builder">The builder.</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="builder"/> is <c>null</c>.</exception>
public static void RegisterDefaultFunctions(this IDirectBuilder<Complex> builder)
{
builder.ThrowIfNull(nameof(builder));
builder.FunctionFound += OnFunctionFound;
}

private static void OnFunctionFound(object sender, FunctionFoundEventArgs<Complex> args)
{
if (!args.Created && Defaults.TryGetValue(args.Function.Name, out var definition))
{
var arguments = new Complex[args.Function.Arguments.Count];
for (var i = 0; i < arguments.Length; i++)
arguments[i] = args.Builder.Build(args.Function.Arguments[i]);
args.Result = definition(arguments);
}
}

// No-argument functions
private static Complex Random(Complex[] args) { args.Check(0); return _rnd.NextDouble(); }

// One-argument functions
private static Complex Abs(Complex[] args) => args.Check(1)[0].Magnitude;
private static Complex Sgn(Complex[] args) => Math.Sign(args.Check(1)[0].Real);
private static Complex Sqrt(Complex[] args) => HelperFunctions.Sqrt(args.Check(1)[0]);
private static Complex URamp(Complex[] args) => HelperFunctions.Ramp(args.Check(1)[0]);
private static Complex U(Complex[] args) => HelperFunctions.Step(args.Check(1)[0]);
private static Complex U2(Complex[] args) => HelperFunctions.Step2(args.Check(1)[0]);
private static Complex Sin(Complex[] args) => Complex.Sin(args.Check(1)[0]);
private static Complex Cos(Complex[] args) => Complex.Cos(args.Check(1)[0]);
private static Complex Tan(Complex[] args) => Complex.Tan(args.Check(1)[0]);
private static Complex Asin(Complex[] args) => Complex.Asin(args.Check(1)[0]);
private static Complex Acos(Complex[] args) => Complex.Acos(args.Check(1)[0]);
private static Complex Atan(Complex[] args) => Complex.Atan(args.Check(1)[0]);
private static Complex Sinh(Complex[] args) => Complex.Sinh(args.Check(1)[0]);
private static Complex Cosh(Complex[] args) => Complex.Cosh(args.Check(1)[0]);
private static Complex Tanh(Complex[] args) => Complex.Tanh(args.Check(1)[0]);
private static Complex Ceil(Complex[] args)
{
var arg = args.Check(1)[0];
return new Complex(Math.Ceiling(arg.Real), Math.Ceiling(arg.Imaginary));
}
private static Complex Floor(Complex[] args)
{
var arg = args.Check(1)[0];
return new Complex(Math.Floor(arg.Real), Math.Floor(arg.Imaginary));
}
private static Complex Exp(Complex[] args) => Complex.Exp(args.Check(1)[0]);
private static Complex Log(Complex[] args) => HelperFunctions.Log(args.Check(1)[0]);
private static Complex Log10(Complex[] args) => HelperFunctions.Log10(args.Check(1)[0]);
private static Complex Square(Complex[] args) { var x = args.Check(1)[0]; return x * x; }
private static Complex Nint(Complex[] args)
{
var arg = args.Check(1)[0];
return new Complex(Math.Round(arg.Real, 0), Math.Round(arg.Imaginary, 0));
}

// Two-argument functions
private static Complex Pow(Complex[] args) { args.Check(2); return Complex.Pow(args[0], args[1]); }
private static Complex Pwr(Complex[] args) { args.Check(2); return HelperFunctions.Power(args[0], args[1]); }
private static Complex Pwrs(Complex[] args) { args.Check(2); return HelperFunctions.Power2(args[0], args[1]); }
private static Complex Min(Complex[] args) { args.Check(2); return Math.Min(args[0].Real, args[1].Real); }
private static Complex Max(Complex[] args) { args.Check(2); return Math.Max(args[0].Real, args[1].Real); }
private static Complex Round(Complex[] args)
{
var arg = args.Check(2)[0];
var n = (int)args[1].Real;
return new Complex(Math.Round(arg.Real, n), Math.Round(arg.Imaginary, n));
}
private static Complex Atan2(Complex[] args) { args.Check(2); return Math.Atan2(args[0].Real, args[1].Real); }
private static Complex Hypot(Complex[] args) { args.Check(2); return HelperFunctions.Hypot(args[0], args[1]); }

// Three-argument functions
private static Complex If(Complex[] args) { args.Check(3); return args[0].Real > 0.5 ? args[1] : args[2]; }

// N-argument functions
private static Complex Pwl(Complex[] args)
{
if (args.Length < 3)
throw new ArgumentMismatchException(3, args.Length);
int points = (args.Length - 1) / 2;
if (args.Length % 2 == 0)
throw new ArgumentMismatchException(points * 2 + 1, args.Length);

var data = new Point[points];
for (var i = 0; i < points; i++)
data[i] = new Point(args[i * 2 + 1].Real, args[i * 2 + 2].Real);
return HelperFunctions.Pwl(args[0].Real, data);
}
private static Complex PwlDerivative(Complex[] args)
{
if (args.Length < 3)
throw new ArgumentMismatchException(3, args.Length);
int points = (args.Length - 1) / 2;
if (args.Length % 2 == 0)
throw new ArgumentMismatchException(points * 2 + 1, args.Length);

var data = new Point[points];
for (var i = 0; i < points; i++)
data[i] = new Point(args[i * 2 + 1].Real, args[i * 2 + 2].Real);
return HelperFunctions.PwlDerivative(args[0].Real, data);
}
}
}
62 changes: 62 additions & 0 deletions SpiceSharpBehavioral/Builders/Direct/FunctionFoundEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using SpiceSharp;
using SpiceSharpBehavioral.Parsers.Nodes;
using System;

namespace SpiceSharpBehavioral.Builders.Direct
{
/// <summary>
/// Event arguments for when a function has been found.
/// </summary>
/// <typeparam param="T">The value type.</typeparam>
public class FunctionFoundEventArgs<T> : EventArgs
{
/// <summary>
/// Gets the builder.
/// </summary>
/// <value>
/// The builder.
/// </value>
public IDirectBuilder<T> Builder { get; }

/// <summary>
/// The function.
/// </summary>
public FunctionNode Function { get; }

/// <summary>
/// Gets or sets the result of the function.
/// </summary>
public T Result
{
get => _result;
set
{
_result = value;
Created = true;
}
}
private T _result;

/// <summary>
/// Gets a flag whether ojr not the function result has been assigned.
/// </summary>
/// <value>
/// <c>true</c> if the function result was assigned; otherwise, <c>false</c>.
/// </value>
public bool Created { get; private set; }

/// <summary>
/// Initializes a new instance of the <see cref="FunctionFoundEventArgs{T}"/> class.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="function">The function node.</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="builder"/> or <paramref name="function"/> is <c>null</c>.</exception>
public FunctionFoundEventArgs(IDirectBuilder<T> builder, FunctionNode function)
{
Builder = builder.ThrowIfNull(nameof(builder));
Function = function.ThrowIfNull(nameof(function));
_result = default;
Created = false;
}
}
}
Loading

0 comments on commit ff07aed

Please sign in to comment.