Skip to content

Commit

Permalink
Add support for BigInteger in CPython engine (#10830)
Browse files Browse the repository at this point in the history
Support is added for encoding decoding BigInteger values from Python
int that have a size that exceeds what can fit in a long.

Also a BigInteger encoder/decoder is added to the custom encoders of
Python.NET in order to support encoding and decoding in the context of
function calls.
  • Loading branch information
mmisol authored Jul 2, 2020
1 parent e6845c7 commit af43ba9
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 24 deletions.
68 changes: 48 additions & 20 deletions src/Libraries/DSCPython/CPythonEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq;
using System.Reflection;
using Autodesk.DesignScript.Runtime;
using DSCPython.Encoders;
using Dynamo.Utilities;
using Python.Runtime;

Expand All @@ -24,6 +25,10 @@ internal delegate void EvaluationEventHandler(EvaluationState state,
[IsVisibleInDynamoLibrary(false)]
public static class CPythonEvaluator
{
static CPythonEvaluator()
{
InitializeEncoders();
}

/// <summary>
/// Executes a Python script with custom variable names. Script may be a string
Expand Down Expand Up @@ -101,6 +106,17 @@ public static object EvaluatePythonScript(
}
}

/// <summary>
/// Registers custom encoders and decoders with the Python.NET runtime
/// </summary>
private static void InitializeEncoders()
{
var encoders = new IPyObjectEncoder[] { new BigIntegerEncoder() };
var decoders = encoders.Cast<IPyObjectDecoder>().ToArray();
Array.ForEach(encoders, e => PyObjectConversions.RegisterEncoder(e));
Array.ForEach(decoders, d => PyObjectConversions.RegisterDecoder(d));
}

/// <summary>
/// Gets the trace back message from the exception, if it is a PythonException.
/// </summary>
Expand Down Expand Up @@ -177,35 +193,47 @@ public static DataMarshaler OutputMarshaler
{
if (PyList.IsListType(pyObj))
{
var pyList = new PyList(pyObj);
var list = new List<object>();
foreach (PyObject item in pyList)
using (var pyList = new PyList(pyObj))
{
list.Add(outputMarshaler.Marshal(item));
var list = new List<object>();
foreach (PyObject item in pyList)
{
list.Add(outputMarshaler.Marshal(item));
}
return list;
}
return list;
}
else if (PyDict.IsDictType(pyObj))
if (PyDict.IsDictType(pyObj))
{
var pyDict = new PyDict(pyObj);
var dict = new Dictionary<object, object>();
foreach (PyObject item in pyDict.Items())
using (var pyDict = new PyDict(pyObj))
{
dict.Add(
outputMarshaler.Marshal(item.GetItem(0)),
outputMarshaler.Marshal(item.GetItem(1))
);
var dict = new Dictionary<object, object>();
foreach (PyObject item in pyDict.Items())
{
dict.Add(
outputMarshaler.Marshal(item.GetItem(0)),
outputMarshaler.Marshal(item.GetItem(1))
);
}
return dict;
}
return dict;
}
else if (PyLong.IsLongType(pyObj))
if (PyLong.IsLongType(pyObj))
{
return PyLong.AsLong(pyObj).ToInt64();
}
else
{
return outputMarshaler.Marshal(pyObj.AsManagedObject(typeof(object)));
using (var pyLong = PyLong.AsLong(pyObj))
{
try
{
return pyLong.ToInt64();
}
catch (PythonException exc) when (exc.Message.StartsWith("OverflowError"))
{
return pyLong.ToBigInteger();
}
}
}

return outputMarshaler.Marshal(pyObj.AsManagedObject(typeof(object)));
});
}
return outputMarshaler;
Expand Down
2 changes: 2 additions & 0 deletions src/Libraries/DSCPython/DSCPython.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Numerics" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
Expand All @@ -60,6 +61,7 @@
<Compile Include="..\..\AssemblySharedInfoGenerator\AssemblySharedInfo.cs">
<Link>Properties\AssemblySharedInfo.cs</Link>
</Compile>
<Compile Include="Encoders\BigIntegerEncoder.cs" />
<Compile Include="CPythonEvaluator.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs">
Expand Down
39 changes: 39 additions & 0 deletions src/Libraries/DSCPython/Encoders/BigIntegerEncoder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.Numerics;
using Python.Runtime;

namespace DSCPython.Encoders
{
internal class BigIntegerEncoder : IPyObjectEncoder, IPyObjectDecoder
{
public bool CanDecode(PyObject objectType, Type targetType)
{
return targetType == typeof(BigInteger);
}

public bool CanEncode(Type type)
{
return type == typeof(BigInteger);
}

public bool TryDecode<T>(PyObject pyObj, out T value)
{
if (!PyLong.IsLongType(pyObj))
{
value = default;
return false;
}

using (var pyLong = PyLong.AsLong(pyObj))
{
value = (T)(object)pyLong.ToBigInteger();
return true;
}
}

public PyObject TryEncode(object value)
{
return new PyLong(value.ToString());
}
}
}
6 changes: 6 additions & 0 deletions test/Engine/FFITarget/DummyMath.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;

namespace FFITarget
{
Expand All @@ -10,6 +11,11 @@ public static double Sum(IEnumerable<double> values)
return values.Sum();
}

public static BigInteger Sum(BigInteger bigNumber1, BigInteger bigNumber2)
{
return bigNumber1 + bigNumber2;
}

public int a { get; set; }

public static DummyMath ValueCtor(int _a)
Expand Down
1 change: 1 addition & 0 deletions test/Engine/FFITarget/FFITarget.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Numerics" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
Expand Down
2 changes: 2 additions & 0 deletions test/Libraries/DynamoPythonTests/IronPythonTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,14 @@
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Numerics" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\..\src\AssemblySharedInfoGenerator\AssemblySharedInfo.cs">
<Link>Properties\AssemblySharedInfo.cs</Link>
</Compile>
<Compile Include="CodeCompletionTests.cs" />
<Compile Include="PythonEvalTestsWithLibraries.cs" />
<Compile Include="PythonEvalTests.cs" />
<Compile Include="PythonEditTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
Expand Down
3 changes: 0 additions & 3 deletions test/Libraries/DynamoPythonTests/PythonEditTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -432,9 +432,6 @@ public void ReturnIronPythonDictionary_AsDynamoDictionary()
}

[Test]
[Category("Failure")]
// This test is failing for CPython3 Engine.
// The BigIntegers which use more than int64 type, are currently not being marshalled.
public void BigInteger_CanBeMarshaledAsInt64()
{
// open test graph
Expand Down
47 changes: 47 additions & 0 deletions test/Libraries/DynamoPythonTests/PythonEvalTestsWithLibraries.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using Dynamo;
using NUnit.Framework;
using static DSIronPythonTests.IronPythonTests;

namespace DynamoPythonTests
{
public class PythonEvalTestsWithLibraries : DynamoModelTestBase
{
protected override void GetLibrariesToPreload(List<string> libraries)
{
libraries.Add("FFITarget.dll");
}

public IEnumerable<PythonEvaluatorDelegate> Evaluators = new List<PythonEvaluatorDelegate> {
DSCPython.CPythonEvaluator.EvaluatePythonScript,
DSIronPython.IronPythonEvaluator.EvaluateIronPythonScript
};

[Test]
public void TestBigIntegerEncoding()
{
string code = @"
import sys
import clr
clr.AddReference('FFITarget')
from FFITarget import DummyMath
# Provide Python int as arguments: Python => .NET
sum = DummyMath.Sum(11111111111111111111, 11111111111111111111)
# sum contains a BigInteger and we use it in Python + operation: .NET => Python
sum = sum + 1
OUT = sum
";
var empty = new ArrayList();
var expected = BigInteger.Parse("22222222222222222223");
foreach (var pythonEvaluator in Evaluators)
{
var result = pythonEvaluator(code, empty, empty);
Assert.AreEqual(expected, result);
}
}
}
}
2 changes: 1 addition & 1 deletion test/core/python/BigIntegerToLong.dyn
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
"HasRunWithoutCrash": true,
"IsVisibleInDynamoLibrary": true,
"Version": "2.7.0.8544",
"RunType": "Automatic",
"RunType": "Manual",
"RunPeriod": "1000"
},
"Camera": {
Expand Down

0 comments on commit af43ba9

Please sign in to comment.