Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrong behavior in float values using IronPython in a C# application #841

Open
jandres861 opened this issue Aug 22, 2024 · 4 comments
Open

Comments

@jandres861
Copy link

Description

We are running a sandbox in a .NET windows application (.NET framework 4.7.2 and IronPython 2.7).

Once we call the execution of the specific script, they returned to us a DivisionByZero exception, because a normal decimal value we are sending from .NET are being converted to a very small number (1.03e-311), that causes the Zero Division issue.

Steps to Reproduce

  1. Create the Runtime and Scope information
  2. Load the Python code to execute (only has several arithmetic operations, nothing very complex neither using arrays or collections)
  3. We receive the exception of the execution in a Try/Catch block in .NET.
  4. We "debug" the code using a direct RaiseError and we identified the runtime is assuming a very small number tending to zero, that is the reason of the fail.

Error returned:

"An error occured while executing the underlying Python script (Traceback (most recent call last): :: File "", line 98, in GetTheFailureMode :: File "", line 699, in GetMaxFfsLoss :: File "", line 2043, in GetMaxHoopLoss :: File "", line 2197, in GetHoopMsop :: ZeroDivisionError: Attempted to divide by zero.)"

Parameters from .NET:

"
addToIerance = true
depthPercentage = 7
designFactor = 0.72
designPressureInMpa = 14.1
inBend = false
lengthInMeter = 0.0689999999999884
lineType = "Hybrid"
outerDiameterInMeter = 0.1683
pipeGrade = "X60"
pofDefectCIass = "General"
wallThicknessInMeter = 0.0095
widthInMeter = 0.038
"

Values recovered in Python just calling the method:

"
An error occured while executing the underlying Python script (Traceback (most recent call last): :: File "", line 148, in GetMaxIliFfsLoss :: Exception: (

'WT: 6.95238308496e-310',
'Hybrid', <type 'float'>,
'Float Number: ', 1.034294700449062e-311,
'Float Number String', '0.0095',
'Convert From String To Float', 1.034294700449062e-311,
'Culture Number Format', <System.Globalization.NumberFormatInfo object at 0x000000000000002B [System.Globalization.NumberFormatInfo]>, 1.0343456283479938e-311,
False,
'0.0095',
1.034294700449062e-311,
1.0353302339822869e-311,
1.034294700449062e-311,
1.0334119502000876e-311,
True,
1.0338363493581819e-311,
'X60',
1.0349533679427365e-311))
"

Culture info:

NumberFormat
CurrencyDecimaIDigits = 2
CurrencyDecimalSeparator = "."
CurrencyGroupSeparator = ","


Expected behavior: Do not convert the normal float parameters to very small numbers.

Actual behavior: The float values are converted to a very small numbers that causes a Zero Division Exception.

Versions

IronPython 2.7
.NET Framework 4.7.2

@slozier
Copy link
Contributor

slozier commented Aug 22, 2024

How are you passing the parameters from .NET to Python? It's hard to tell what your doing from the description above. An example to reproduce the issue would be helpful...

@jandres861
Copy link
Author

jandres861 commented Aug 22, 2024

How are you passing the parameters from .NET to Python? It's hard to tell what your doing from the description above. An example to reproduce the issue would be helpful...

Hi, thanks for your quick answer. Let me write some example of the code here, limited as by my company's IT policies ( I'm trying to include screenshots but is not possible for some reason)

//Create the Runtime
public RuntimeProvider()
        {
            // set the callback to log errors.
            var options = new Dictionary<string, object>();
            options["Debug"] = false;

            // Creates execution engine
            this.myEngine = Python.CreateEngine(options);

            // This is for debugging Python code
            // this.myEngine.SetTrace(this.OnTraceback);
            this.myScope = this.myEngine.CreateScope();
        }

// Load the script
public void LoadScript(string scriptSource, bool sameScope, bool sourceIsFileName)
        {
           
                this.ScriptScope = this.RuntimeProvider.CreateScope();
                       ScriptSource source;

                         source = this.RuntimeProvider.Engine.CreateScriptSourceFromString(scriptSource);
         
            ScriptSources.Add(source);
            
            var compiled = source.Compile();
            this.myExecutions.Add(compiled.Execute(this.ScriptScope));
        }

// Proxy
        private TcoAlgorithmProxy LoadAlgorithmInstance(string algorithmCode, string algorithmDescriptionBuilder)
        {
            var scriptLoader = new ScriptLoader();
            var pythonInstance = this.LoadAlgorithm(algorithmCode, algorithmDescriptionBuilder, scriptLoader);

            // Creates the proxy to comunicate the code with the other application domain
            // This proxy is neccesary because the object that you want to sent to the other domain must intherit from MarshalByRefObject
            var algorithm = new AlgorithmProxy(pythonInstance, scriptLoader.RuntimeProvider.Engine);
            return algorithm;
        }


// Call function
protected override double? GetValue(IClusterableFeature anomaly)
        {
            var ffsLoss = this.FfsAssessement.GetMaxIliFfsLoss(anomaly, lineType.Value, inBend.Value, pipeGrade.Value, anomaly.AddTolerance);
            return ffsLoss.Value;
        }

// Specific function call in python
public double GetMaxIliFfsLoss(
            string lineType,
            double wallThicknessInMeter,
            double designFactor,
            bool inBend,
            string pofDefectClass,
            double lengthInMeter,
            double widthInMeter,
            double depthPercentage,
            double designPressureInMpa,
            string pipeGrade,
            double outerDiameterInMeter,
            bool addTolerance)
        {
            return this.GetValue(
                () => this.myPythonAlgorithmInstance.GetMaxIliFfsLoss(
                    lineType,
                    wallThicknessInMeter,
                    designFactor,
                    inBend,
                    wallThicknessInMeter.ToString("R"),
                    lengthInMeter,
                    widthInMeter,
                    depthPercentage,
                    designPressureInMpa,
                    pipeGrade,
                    outerDiameterInMeter,
                    addTolerance));

And finally the simplified code in Python that is being executed:

def GetMaxIliFfsLoss(self,
lineType,
wallThicknessInMeter,
designFactor,
inBend,
pofDefectClass,
lengthInMeter,
widthInMeter,
depthPercentage,
designPressureInMpa,
pipeGrade,
outerDiameterInMeter,
addTolerance):
if not isinstance(wallThicknessInMeter, float):
culture = CultureInfo.CurrentCulture
number = 0.00950000
numberStr = number.ToString(culture.NumberFormat)
raise Exception("WT: " + str(wallThicknessInMeter), lineType,
type(wallThicknessInMeter),
"Float Number: ",
number,
"Float Number String",
numberStr,
"Convert From String To Float",
Double.Parse("0.0095"),
"Culture Number Format",
culture.NumberFormat,
designFactor,
inBend,
pofDefectClass,
float(pofDefectClass),
lengthInMeter,
widthInMeter,
depthPercentage,
addTolerance,
designPressureInMpa,
pipeGrade,
outerDiameterInMeter)

And just using that RaiseError to print in the .NET Exception the values we identified the change to that very small numbers, not the desired ones.

@slozier
Copy link
Contributor

slozier commented Aug 26, 2024

It's interesting that Double.Parse("0.0095") would give you 1.034294700449062e-311. Is the code only failing once in a while or is it consistently incorrect? Unfortunately I haven't been able to reproduce. Which version of IronPython 2.7 and of the DLR are you using?

@jandres861
Copy link
Author

It's interesting that Double.Parse("0.0095") would give you 1.034294700449062e-311. Is the code only failing once in a while or is it consistently incorrect? Unfortunately I haven't been able to reproduce. Which version of IronPython 2.7 and of the DLR are you using?

As we are iterating over several calls to the python functions, the code works well in the first ones but after that starts to fail and we could never have a full execution of the algorithm.

The specific version of IronPython is 2.7.9. The solution is build in .NET framework 4.7.2. Seems also related with the Microsoft.Scripting 1.2.2.0.

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants