Bug with parallel solves using Ampl API (C#)

Hi,

I am using the latest version of Ampl CE with the C# API.
I need to launch parallel solves of independant problems. Since it did not work, I coded a simple example to illustrate :

        public void TestAmplParallel()
        {
            int nbTest = 20;
            Stopwatch timer = new Stopwatch();
            object locker = new object();

            Console.WriteLine("Start parallel test...");
            timer.Restart();
            Parallel.For(0, nbTest, i =>
            {
                lock (locker)
                {
                    File.Copy("Modele.mod", $"Modele_{i}.mod", true);
                    File.Copy("Modele.dat", $"Modele_{i}.dat", true);
                }

                var ampl = new AMPL();
                ampl.EnableOutputRouting();
                ampl.Read($"Modele_{i}.mod");
                ampl.ReadData($"Modele_{i}.dat");
                ampl.Solve("", "cbc");
                var objValue = ampl.GetObjective("obj").Value;
                Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} : obj = {objValue}");
            });

            Console.WriteLine($"Duration : {timer.Elapsed.TotalSeconds} seconds");
            Console.ReadKey();
        }

The first optimizations succeed but it freezes at some times. Using debugger, it shows that we are stuck at “var ampl = new AMPL();” for the remaining threads.

I suspected an issue from the licence system since amplkey.log indicated messages like :
2025/01/13 22:34:59 Cannot unlock “D:\Dev/amplkey.lock”, reason: remove D:\Dev/amplkey.lock: The process cannot access the file because it is being used by another process.

So I tried another version calling directly the executable without api calls :

        public void TestAmplParallel2()
        {
            int nbTest = 20;
            Stopwatch timer = new Stopwatch();
            object locker = new object();

            Console.WriteLine("Start parallel test...");
            timer.Restart();
            Parallel.For(0, nbTest, i =>
            {
                lock (locker)
                {
                    File.Copy("Modele.mod", $"Modele_{i}.mod", true);
                    File.Copy("Modele.dat", $"Modele_{i}.dat", true);
                }

                StringWriter sw = new StringWriter();
                sw.WriteLine($"model 'Modele_{i}.mod';");
                sw.WriteLine($"data 'Modele_{i}.dat';");
                sw.WriteLine($"option solver cbc;");
                sw.WriteLine($"solve;");
                sw.WriteLine($"print _obj[1] > 'Modele_{i}.sol';");
                File.WriteAllText($"Modele_{i}.run", sw.ToString(), Encoding.GetEncoding(1252));
                string arguments = string.Format(@"""{0}""", $"Modele_{i}.run");

                var process = new Process();
                process.StartInfo.FileName = "ampl.exe";
                process.StartInfo.WorkingDirectory = Directory.GetCurrentDirectory();
                process.StartInfo.Arguments = arguments;
                process.StartInfo.CreateNoWindow = true;
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError = true;
                process.StartInfo.RedirectStandardInput = true;
                process.Start();

                process.WaitForExit();

                var objValue = File.ReadAllText($"Modele_{i}.sol");
                Console.Write($"Thread {Thread.CurrentThread.ManagedThreadId} : obj = {objValue}");
            });

            Console.WriteLine($"Duration : {timer.Elapsed.TotalSeconds} seconds");
            Console.ReadKey();
        }

It works perfectly (even when incresaing the number of tests). I conclude that the problem is not coming from the licence manager but from the api.
Any help would be very appreciate

Modele.dat (199 Bytes)
Modele.mod (304 Bytes)

Hi @Nico_ampl,

Thank you for reaching out. The issue may be related to the file system of drive D. To use Community Edition licenses, AMPL should be installed on the user home folder in drive C. It needs an writable NTFS file system in order to perform atomic file updates.

Could you please try try again with AMPL installed on your home folder?

Thank you for your answer.

I don’t install Ampl CE. I always copy exe and all the necessary files where I need them.
All my drives are NTFS. I am using Windows 11.

It has always worked perfectly like this when using the executable instead of api. Even on other drive than C.
For example TestAmplParallel2() works perfectly on my D.

With the api, I made tests which also worked normally on drive D. The problem appears only with parallelism use of api.

I will try tomorrow by copying the files on drive C.

Since the drive is NTFS, could you please send us the amplkey.log file? It should indicate what is causing the lock file to stay there.

I have launched the two methods on my user directory using drive C. The problem is still the same.

You will find attached the two log files (I have changed extension since it cannot be uploaded otherwise) :

  • amplkeyAPI.txt is the log for the first method. The one using the api and that freezes after solving a certain number of tests
  • amplkeyEXE.txt is the log for the second method. The one using the ampl exe and which works perfectly
    amplkeyAPI.txt (1.7 KB)
    amplkeyEXE.txt (4.8 KB)

I made other tests for isolating the bug. For me there is something wrong when calling the constructor. For example, it freezes with the following code inside the Parallel.For loop :

            AMPL[] amplTab = new AMPL[nbTest];
            Parallel.For(0, nbTest, i =>
            {
                amplTab[i] = new AMPL();
            });

A safe solution seems to use a lock and put inside the call to the AMPL constructor. It is a bit slower but it avoid to be stucked.

Hi Nico,

thank you for the pointers.
Can you try specifying the AMPL installation directory in the constructor - via the Environment object?
E.g.:

ampl.Environment e = new ampl.Environment("path to the AMPL installation directory");
AMPL a = new AMPL(e);

1 Like

It works !
Thank you for your help.

1 Like