using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Runtime.Intrinsics.X86;
using System.Xml.Linq;
using System.Data;
using System.Linq.Expressions;
using System.Reflection.Metadata;
using static System.Net.Mime.MediaTypeNames;

namespace arhiplex
{
    /// <summary>
    /// \~russian
    /// Тип переменной
    /// 
    /// \~english
    /// Variable type
    /// 
    /// \~chinese-traditional
    /// Variable type
    /// </summary>
    public enum Variable_type: int
    {
        /// <summary>\~russian непрерывная \~english continuous \~chinese-traditional continuous</summary>
        continuous = 0,
        /// <summary>\~russian бинарная \~english binary \~chinese-traditional binary</summary>
        binary = 1,
        /// <summary>\~russian целочисленная \~english integer \~chinese-traditional integer</summary>
        integer = 2,
        /// <summary>\~russian полу-непрерывная \~english semicontinuous \~chinese-traditional semicontinuous</summary>
        semicontinuous = 3,
        /// <summary>\~russian полу-целая \~english semiinteger \~chinese-traditional semiinteger</summary>
        semiinteger = 4,
    };

    /// <summary>
    /// \~russian
    /// Направление оптимизации целевой функции
    /// 
    /// \~english
    /// Direction of optimization of the objective function
    /// 
    /// \~chinese-traditional
    /// Direction of optimization of the objective function
    /// </summary>
    public enum Objective_sense: int //dd
    {
        /// <summary>\~russian Минимизация \~english Minimization \~chinese-traditional Minimization</summary>
        minimize = 0,
        /// <summary>\~russian Максимизация \~english Maximization \~chinese-traditional Maximization</summary>
        maximize = 1,
    };

    /// <summary>
    /// \~russian
    /// Знак ограничения между левой частью (выражением) и правой частью (константой)
    /// 
    /// \~english
    /// The constraint sign between the left side (expression) and the right side (constant)
    /// 
    /// \~chinese-traditional
    /// The constraint sign between the left side (expression) and the right side (constant)
    /// </summary>
    public enum Constraint_sense: int
    {
        /// <summary> '==' </summary>
        equal = 0,
        /// <summary> '<=' </summary>
        less_equal = 1,
        /// <summary> '>=' </summary>
        greater_equal = 2,
    };

    /// <summary>
    /// \~russian
    /// Результат процесса расчетов
    /// 
    /// \~english
    /// Result of the calculation process
    /// 
    /// \~chinese-traditional
    /// Result of the calculation process
    /// </summary>
    public enum Solve_result: int
    {
        /// <summary>\~russian Процесс решения успешен - найдено некоторое решение или обнаружена недостижимость модели \~english The solution process is successful - some solution is found or the model is found to be infeasible \~chinese-traditional The solution process is successful - some solution is found or the model is found to be infeasible</summary>
        success = 0,
        /// <summary>\~russian Процесс решения неудачен - не найдено никаких решений и не обнаружена недостижимость модели \~english The solution process failed - no solutions were found and no model infeasibility was detected \~chinese-traditional The solution process failed - no solutions were found and no model infeasibility was detected</summary>
        fail = 1,
        /// <summary>\~russian Невалидный ключ для удаленного расчета \~english Invalid key for remote calculation \~chinese-traditional Invalid key for remote calculation</summary>
        remote_invalid_api_key = 2,
        /// <summary>\~russian Переменная окружения X_API_KEY не установлена \~english Environment variable X_API_KEY is not set \~chinese-traditional Environment variable X_API_KEY is not set</summary>
        remote_api_key_not_set = 3,
        /// <summary>\~russian Не осталось доступного времени для осуществления удаленного расчета \~english There is no time left to perform remote calculation \~chinese-traditional There is no time left to perform remote calculation</summary>
        remote_time_amount_is_over = 4,
        /// <summary>\~russian Превышен лимит времени для единичного удаленного расчета. Параметр time_limit нарушает ограничения лицензии \~english Time limit for single remote calculation exceeded. The time_limit parameter violates license restrictions \~chinese-traditional Time limit for single remote calculation exceeded. The time_limit parameter violates license restrictions</summary>
        remote_time_per_calc_violated = 5,
        /// <summary>\~russian Удаленный расчет неудачен \~english Remote calculation failed \~chinese-traditional Remote calculation failed</summary>
        remote_fail = 6,
    };

    /// <summary>
    /// \~russian
    /// Статус решения по результатам расчета
    /// 
    /// \~english
    /// The status of the solution based on the calculation results
    /// 
    /// \~chinese-traditional
    /// The status of the solution based on the calculation results
    /// </summary>
    public enum Solution_status: int
    {
        /// <summary>\~russian неопределенное/невалидное значение \~english undefined/invalid value \~chinese-traditional undefined/invalid value</summary>
        invalid_solution,
        /// <summary>\~russian решение оптимально (погрешность в рамках заданного значения) \~english the solution is optimal (error within the specified value) \~chinese-traditional the solution is optimal (error within the specified value)</summary>
        optimal,
        /// <summary>\~russian решение найдено, но не оптимально (погрешность > заданного значения) \~english solution found, but not optimal (error > specified value) \~chinese-traditional solution found, but not optimal (error > specified value)</summary>
        feasible,
        /// <summary>\~russian модель не имеет решения (решение недостижимо) \~english the model has no solution (the solution is infeasible) \~chinese-traditional the model has no solution (the solution is infeasible)</summary>
        infeasible,
        /// <summary>\~russian модель неограничена (целевая функция может бесконечно неограниченно увеличиваться/уменьшаться) \~english the model is unbounded (the objective function can increase/decrease infinitely without limit) \~chinese-traditional the model is unbounded (the objective function can increase/decrease infinitely without limit)</summary>
        unbounded,
        /// <summary>\~russian модель недостижима или неограничена \~english model is infeasible or unbounded \~chinese-traditional model is infeasible or unbounded</summary>
        infeasible_or_unbounded,
    };


    /// <summary>
    /// \~russian
    /// Исключение, генерируемое всеми классами в случае ошибки
    /// 
    /// \~english
    /// Exception thrown by all classes in case of an error
    /// 
    /// \~chinese-traditional
    /// Exception thrown by all classes in case of an error
    /// </summary>
    public class ArhiplexException : Exception
    {
        public ArhiplexException(int err_code, string message)
            : base(message)
        {
        }
    }

    internal class StringConverter
    {
        public static string? StringFromArhiplex(IntPtr native)
        {
            if (native == IntPtr.Zero)
                return null;
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                return Marshal.PtrToStringAnsi(native) ?? "";
            return Marshal.PtrToStringUTF8(native) ?? "";
        }
    }


    /// <summary>
    /// \~russian
    /// Предоставляет функциии чтения/записи файлов, модификации модели, 
    /// управления переменными, ограничениями, а также целевой функцией
    /// 
    /// \~english
    /// Provides functions for reading/writing files, modifying the model,
    /// managing variables, constraints, and the objective function
    /// 
    /// \~chinese-traditional
    /// Provides functions for reading/writing files, modifying the model,
    /// managing variables, constraints, and the objective function
    /// </summary>
    public class Model : IDisposable
    {
        #region arhiplex_imports
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl)]
        private static extern double Arhiplex_Inf();

        public static double Inf
        {
            get
            {
                return Arhiplex_Inf();
            }
        }

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr Model_Create();

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl)]
        private static extern void Model_Release(IntPtr model);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_Read(IntPtr model, string file_name);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_ReadMps(IntPtr model, string file_name);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_ReadLp(IntPtr model, string file_name);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr Model_GetName(IntPtr model);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_Write(IntPtr model, string file_name, string name_mapping_file);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_WriteMps(IntPtr model, string file_name, string name_mapping_file);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_WriteLp(IntPtr model, string file_name, string name_mapping_file);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern int Model_GetIntParam(IntPtr model, string param_name);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern double Model_GetDblParam(IntPtr model, string param_name);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr Model_GetStringParam(IntPtr model, string param_name);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern int Model_GetBoolParam(IntPtr model, string param_name);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_SetIntParam(IntPtr model, string param_name, int nVal);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_SetDblParam(IntPtr model, string param_name, double dVal);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_SetStringParam(IntPtr model, string param_name, string cVal);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_SetBoolParam(IntPtr model, string param_name, int bVal);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_SetName(IntPtr model, string model_name);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern int Model_GetErrorCode(IntPtr model);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr Model_GetErrorText(IntPtr model);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr Model_AddVariable(IntPtr model, double lb, double ub, double obj, Variable_type var_type, string name);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr Model_AddConstraintLinear1(IntPtr model, IntPtr expr, Constraint_sense sense, double rhs, string name);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr Model_AddConstraintQuad1(IntPtr model, IntPtr quad_expr, Constraint_sense sense, double rhs, string name);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr Model_AddConstraintLinear2(IntPtr model, IntPtr lin_exexpr, double lb, double ub, string name);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr Model_AddConstraintQuad2(IntPtr model, IntPtr quad_expr, double lb, double ub, string name);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr Model_AddConstraint(IntPtr model, IntPtr constr, string name);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern int Model_GetVariablesCount(IntPtr model);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr Model_GetVariable(IntPtr model, int var_idx);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr Model_GetVariableByName(IntPtr model, string name);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern int Model_GetConstraintsCount(IntPtr model);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr Model_GetConstraint(IntPtr model, int constr_idx);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr Model_GetConstraintByName(IntPtr model, string name);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_SetObjectiveSense(IntPtr model, Objective_sense sense);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern Objective_sense Model_GetObjectiveSense(IntPtr model);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_SetObjectiveOffset(IntPtr model, double offset);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_SetObjectiveLinear(IntPtr model, IntPtr expr, Objective_sense sense);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_SetObjectiveQuad(IntPtr model, IntPtr quad_expr, Objective_sense sense);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_ClearObjective(IntPtr model);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr Model_GetObjective(IntPtr model);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr Model_GetQuadObjective(IntPtr model);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr Model_Solve(IntPtr model);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr Model_SolveRemote(IntPtr model, string name_mapping_file);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_SolveRemoteAsync(IntPtr model, string name_mapping_file);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr Model_GetRemoteSolveLog(IntPtr model, string szCalcUID);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_RemoveVar(IntPtr model, IntPtr var);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_RemoveConstr(IntPtr model, IntPtr constr);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_Clear(IntPtr model);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_SetLogFile(IntPtr model, string log_file);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_SetLogCallback(IntPtr model, IntPtr log_callback);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_AddMipStartValueByName(IntPtr model, string var_name, double var_value);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_AddMipStartValue(IntPtr model, IntPtr var, double var_value);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_AddMipStartValues(IntPtr model, string sol_file);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Model_ClearMipStartValues(IntPtr model);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr Model_GetCalcUID(IntPtr model);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern int Model_IsMip(IntPtr model);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern int Model_IsNonlinear(IntPtr model);

        #endregion

        /// <summary>
        /// \~russian
        /// Создает экземпляр модели
        /// <param name="name">Имя модели</param>
        /// 
        /// \~english
        /// Creates an instance of the model
        /// <param name="name">The name of the model</param>
        /// 
        /// \~chinese-traditional
        /// Creates an instance of the model
        /// <param name="name">The name of the model</param>
        /// </summary>
        public Model(string name = "")
        {
            model_ = Model_Create();
            if(name.Length > 0)
                SetName(name);
        }

        ~Model()
        {
            Cleanup();
        }

        private void Cleanup()
        {
            if (model_ != IntPtr.Zero)
            {
                Model_Release(model_);
                model_ = IntPtr.Zero;
            }
        }

        public void Dispose()
        {
            Cleanup();

            // Prevent the object from being placed on the
            // finalization queue
            System.GC.SuppressFinalize(this);
        }

        /// <summary>
        /// \~russian
        /// Читает модель из файла, тип модели определяется по расширению
        /// <param name="file_name">путь к файлу модели</param>
        /// 
        /// \~english
        /// Reads a model from a file, the model type is determined by the extension
        /// <param name="file_name">path to the model file</param>
        /// 
        /// \~chinese-traditional
        /// Reads a model from a file, the model type is determined by the extension
        /// <param name="file_name">path to the model file</param>
        /// </summary>
        public void Read(string file_name)
        {
            Model_Read(model_, file_name);
            CheckError();
        }


        /// <summary>
        /// \~russian
        /// Читает модель из файла, при этом он будет читаться как MPS файл независимо от расширения
        /// <param name="file_name">путь к файлу модели</param>
        /// 
        /// \~english
        /// Reads a model from a file, it will be read as an MPS file regardless of the extension
        /// <param name="file_name">path to the model file</param>
        /// 
        /// \~chinese-traditional
        /// Reads a model from a file, it will be read as an MPS file regardless of the extension
        /// <param name="file_name">path to the model file</param>
        /// </summary>
        public void ReadMps(string file_name)
        {
            Model_ReadMps(model_, file_name);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Читает модель из файла, при этом он будет читаться как LP файл независимо от расширения
        /// <param name="file_name">путь к файлу модели</param>
        /// 
        /// \~english
        /// Reads a model from a file, it will be read as an LP file regardless of the extension
        /// <param name="file_name">path to the model file</param>
        /// 
        /// \~chinese-traditional
        /// Reads a model from a file, it will be read as an LP file regardless of the extension
        /// <param name="file_name">path to the model file</param>
        /// </summary>
        public void ReadLp(string file_name)
        {
            Model_ReadLp(model_, file_name);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Записывает модель в файл. Тип будет определен по расширению
        /// <param name="file_name">путь к записываемому файлу</param>
        /// <param name="name_mapping_file">путь к файлу, который будет содержать
        /// соответствие между именами модели при анонимизации (если пустой - запись
        /// без анонимизации)
        /// </param>
        /// 
        /// \~english
        /// Writes the model to a file. The type will be determined by the extension
        /// <param name="file_name">path to the file being written</param>
        /// <param name="name_mapping_file">path to the file that will contain
        /// the correspondence between the model names during anonymization (if empty - write
        /// without anonymization)
        /// </param>
        /// 
        /// \~chinese-traditional
        /// Writes the model to a file. The type will be determined by the extension
        /// <param name="file_name">path to the file being written</param>
        /// <param name="name_mapping_file">path to the file that will contain
        /// the correspondence between the model names during anonymization (if empty - write
        /// without anonymization)
        /// </param>
        /// </summary>
        public void Write(string file_name, string name_mapping_file = "")
        {
            Model_Write(model_, file_name, name_mapping_file);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Записывает модель в файл в формате MPS
        /// <param name="file_name">путь к записываемому файлу</param>
        /// <param name="name_mapping_file">путь к файлу, который будет содержать
        /// соответствие между именами модели при анонимизации (если пустой - запись
        /// без анонимизации)
        /// </param>
        /// 
        /// \~english
        /// Writes the model to a file in MPS format
        /// <param name="file_name">path to the file being written</param>
        /// <param name="name_mapping_file">path to the file that will contain
        /// the correspondence between the model names during anonymization (if empty - write
        /// without anonymization)
        /// </param>
        /// 
        /// \~chinese-traditional
        /// Writes the model to a file in MPS format
        /// <param name="file_name">path to the file being written</param>
        /// <param name="name_mapping_file">path to the file that will contain
        /// the correspondence between the model names during anonymization (if empty - write
        /// without anonymization)
        /// </param>
        /// </summary>
        public void WriteMps(string file_name, string name_mapping_file = "")
        {
            Model_WriteMps(model_, file_name, name_mapping_file);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Записывает модель в файл в формате LP
        /// <param name="file_name">путь к записываемому файлу</param>
        /// <param name="name_mapping_file">путь к файлу, который будет содержать
        /// соответствие между именами модели при анонимизации (если пустой - запись
        /// без анонимизации)
        /// </param>
        /// 
        /// \~english
        /// Writes the model to a file in LP format
        /// <param name="file_name">path to the file being written</param>
        /// <param name="name_mapping_file">path to the file that will contain
        /// the correspondence between the model names during anonymization (if empty - write
        /// without anonymization)
        /// </param>
        /// 
        /// \~chinese-traditional
        /// Writes the model to a file in LP format
        /// <param name="file_name">path to the file being written</param>
        /// <param name="name_mapping_file">path to the file that will contain
        /// the correspondence between the model names during anonymization (if empty - write
        /// without anonymization)
        /// </param>
        /// </summary>
        public void WriteLp(string file_name, string name_mapping_file = "")
        {
            Model_WriteLp(model_, file_name, name_mapping_file);
            CheckError();
        }


        /// <summary>
        /// \~russian
        /// Получает целочисленный параметр
        /// <param name="param_name">имя параметра</param>
        /// <returns>Значение параметра</returns>
        /// 
        /// \~english
        /// Gets an integer parameter
        /// <param name="param_name">parameter name</param>
        /// <returns>Parameter value</returns>
        /// 
        /// \~chinese-traditional
        /// Gets an integer parameter
        /// <param name="param_name">parameter name</param>
        /// <returns>Parameter value</returns>
        /// </summary>
        public int GetIntParam(string param_name)
        {
            var param = Model_GetIntParam(model_, param_name);
            CheckError();
            return param;
        }

        /// <summary>
        /// \~russian
        /// Получает параметр с плавающей точкой
        /// <param name="param_name">имя параметра</param>
        /// <returns>Значение параметра</returns>
        /// 
        /// \~english
        /// Gets a floating point parameter
        /// <param name="param_name">parameter name</param>
        /// <returns>Parameter value</returns>
        /// 
        /// \~chinese-traditional
        /// Gets a floating point parameter
        /// <param name="param_name">parameter name</param>
        /// <returns>Parameter value</returns>
        /// </summary>
        public double GetDblParam(string param_name)
        {
            var param = Model_GetDblParam(model_, param_name);
            CheckError();
            return param;
        }

        /// <summary>
        /// \~russian
        /// Получает строковый параметр
        /// <param name="param_name">имя параметра</param>
        /// <returns>Значение параметра</returns>
        /// 
        /// \~english
        /// Gets a string parameter
        /// <param name="param_name">parameter name</param>
        /// <returns>Parameter value</returns>
        /// 
        /// \~chinese-traditional
        /// Gets a string parameter
        /// <param name="param_name">parameter name</param>
        /// <returns>Parameter value</returns>
        /// </summary>
        public string GetStringParam(string param_name)
        {
            var param = Model_GetStringParam(model_, param_name);
            CheckError();
            return param == IntPtr.Zero ? "" : (StringConverter.StringFromArhiplex(param) ?? "");
        }

        /// <summary>
        /// \~russian
        /// Получает логический параметр
        /// <param name="param_name">имя параметра</param>
        /// <returns>Значение параметра</returns>
        /// 
        /// \~english
        /// Gets a boolean parameter
        /// <param name="param_name">parameter name</param>
        /// <returns>Parameter value</returns>
        /// 
        /// \~chinese-traditional
        /// Gets a boolean parameter
        /// <param name="param_name">parameter name</param>
        /// <returns>Parameter value</returns>
        /// </summary>
        public bool GetBoolParam(string param_name)
        {
            var param = Model_GetBoolParam(model_, param_name);
            CheckError();
            return param != 0;
        }

        /// <summary>
        /// \~russian
        /// Задаёт целочисленный параметр
        /// <param name="param_name">имя параметра</param>
        /// <param name="nVal">Новое значение параметра</param>
        /// 
        /// \~english
        /// Sets an integer parameter
        /// <param name="param_name">parameter name</param>
        /// <param name="nVal">New value of the parameter</param>
        /// 
        /// \~chinese-traditional
        /// Sets an integer parameter
        /// <param name="param_name">parameter name</param>
        /// <param name="nVal">New value of the parameter</param>
        /// </summary>
        public void SetIntParam(string param_name, int nVal)
        {
            Model_SetIntParam(model_, param_name, nVal);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Задаёт параметр с плавающей точкой
        /// <param name="param_name">имя параметра</param>
        /// <param name="dVal">Новое значение параметра</param>
        /// 
        /// \~english
        /// Sets a floating point parameter
        /// <param name="param_name">parameter name</param>
        /// <param name="dVal">New value of the parameter</param>
        /// 
        /// \~chinese-traditional
        /// Sets a floating point parameter
        /// <param name="param_name">parameter name</param>
        /// <param name="dVal">New value of the parameter</param>
        /// </summary>
        public void SetDblParam(string param_name, double dVal)
        {
            Model_SetDblParam(model_, param_name, dVal);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Задаёт строковый параметр
        /// <param name="param_name">имя параметра</param>
        /// <param name="cVal">Новое значение параметра</param>
        /// 
        /// \~english
        /// Sets a string parameter
        /// <param name="param_name">parameter name</param>
        /// <param name="cVal">New value of the parameter</param>
        /// 
        /// \~chinese-traditional
        /// Sets a string parameter
        /// <param name="param_name">parameter name</param>
        /// <param name="cVal">New value of the parameter</param>
        /// </summary>
        public void SetStringParam(string param_name, string cVal)
        {
            Model_SetStringParam(model_, param_name, cVal);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Задаёт логический параметр
        /// <param name="param_name">имя параметра</param>
        /// <param name="bVal">Новое значение параметра</param>
        /// 
        /// \~english
        /// Sets a boolean parameter
        /// <param name="param_name">parameter name</param>
        /// <param name="bVal">New value of the parameter</param>
        /// 
        /// \~chinese-traditional
        /// Sets a boolean parameter
        /// <param name="param_name">parameter name</param>
        /// <param name="bVal">New value of the parameter</param>
        /// </summary>
        public void SetBoolParam(string param_name, bool bVal)
        {
            Model_SetBoolParam(model_, param_name, bVal ? 1 : 0);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Получает имя модели
        /// <returns>Имя модели</returns>
        /// 
        /// \~english
        /// Gets the model name
        /// <returns>Model name</returns>
        /// 
        /// \~chinese-traditional
        /// Gets the model name
        /// <returns>Model name</returns>
        /// </summary>
        public string? GetName()
        {
            IntPtr name = Model_GetName(model_);
            CheckError();
            return StringConverter.StringFromArhiplex(name);
        }

        /// <summary>
        /// \~russian
        /// Задаёт имя модели
        /// <param name="name">Имя модели</param>
        /// 
        /// \~english
        /// Sets the model name
        /// <param name="name">Model name</param>
        /// 
        /// \~chinese-traditional
        /// Sets the model name
        /// <param name="name">Model name</param>
        /// </summary>
        public void SetName(string name)
        {
            Model_SetName(model_, name);
            CheckError();
        }

        private void CheckError()
        {
            if(GetErrorCode() != 0)
            {
                throw new ArhiplexException(GetErrorCode(), GetErrorText()??"");
            }
        }

        private int GetErrorCode()
        {
            return Model_GetErrorCode(model_);
        }


        private string? GetErrorText()
        {
            IntPtr err_text = Model_GetErrorText(model_);
            return StringConverter.StringFromArhiplex(err_text);
        }

        /// <summary>
        /// \~russian
        /// Добавляет переменную в модель с заданными параметрами
        /// <param name="lb">нижняя граница переменной</param>
        /// <param name="ub">верхняя граница переменной</param>
        /// <param name="obj">коэффициент к переменной в целевой функции</param>
        /// <param name="var_type">тип переменной</param>
        /// <param name="name">имя переменной</param>
        /// 
        /// \~english
        /// Adds a variable to the model with the given parameters
        /// <param name="lb">lower bound of the variable</param>
        /// <param name="ub">upper bound of the variable</param>
        /// <param name="obj">coefficient of the variable in the objective function</param>
        /// <param name="var_type">variable type</param>
        /// <param name="name">variable name</param>
        /// 
        /// \~chinese-traditional
        /// Adds a variable to the model with the given parameters
        /// <param name="lb">lower bound of the variable</param>
        /// <param name="ub">upper bound of the variable</param>
        /// <param name="obj">coefficient of the variable in the objective function</param>
        /// <param name="var_type">variable type</param>
        /// <param name="name">variable name</param>
        /// </summary>
        public Variable AddVariable(double lb, double ub, double obj, Variable_type var_type, string name)
        {
            var var_handle = Model_AddVariable(model_, lb, ub, obj, var_type, name);
            CheckError();
            return new Variable(var_handle);
        }

        /// <summary>
        /// \~russian
        /// Добавляет ограничение в модель с заданными параметрами
        /// <param name="lin_expr">линейное выражение для ограничения</param>
        /// <param name="sense">тип ограничения ( <=, >= , == )</param>
        /// <param name="rhs">константное значение в правой части ограничения</param>
        /// <param name="name">имя ограничения. Если передана пустая строка или null, имя ограничения будет сгенерировано</param>
        /// <returns>объект нового ограничения</returns>
        /// 
        /// \~english
        /// Adds a constraint to the model with the given parameters
        /// <param name="lin_expr">linear expression for the constraint</param>
        /// <param name="sense">constraint type ( <=, >= , == )</param>
        /// <param name="rhs">constant value on the right side of the constraint</param>
        /// <param name="name">constraint name. If an empty string or null is passed, the constraint name will be generated</param>
        /// <returns>new constraint object</returns>
        /// 
        /// \~chinese-traditional
        /// Adds a constraint to the model with the given parameters
        /// <param name="lin_expr">linear expression for the constraint</param>
        /// <param name="sense">constraint type ( <=, >= , == )</param>
        /// <param name="rhs">constant value on the right side of the constraint</param>
        /// <param name="name">constraint name. If an empty string or null is passed, the constraint name will be generated</param>
        /// <returns>new constraint object</returns>
        /// </summary>
        public Constraint AddConstraint(LinearExpression lin_expr, Constraint_sense sense, double rhs, string name)
        {
            var constr_handle = Model_AddConstraintLinear1(model_, lin_expr.Get(), sense, rhs, name);
            CheckError();
            return new Constraint(constr_handle);
        }

        /// <summary>
        /// \~russian
        /// Добавляет ограничение в модель с заданными параметрами
        /// <param name="quad_expr">квадратичное выражение для ограничения</param>
        /// <param name="sense">тип ограничения ( <=, >= , == )</param>
        /// <param name="rhs">константное значение в правой части ограничения</param>
        /// <param name="name">имя ограничения. Если передана пустая строка или null, имя ограничения будет сгенерировано</param>
        /// <returns>объект нового ограничения</returns>
        /// 
        /// \~english
        /// Adds a constraint to the model with the given parameters
        /// <param name="quad_expr">quadratic expression for the constraint</param>
        /// <param name="sense">constraint type ( <=, >= , == )</param>
        /// <param name="rhs">constant value on the right side of the constraint</param>
        /// <param name="name">constraint name. If an empty string or null is passed, the constraint name will be generated</param>
        /// <returns>new constraint object</returns>
        /// 
        /// \~chinese-traditional
        /// Adds a constraint to the model with the given parameters
        /// <param name="quad_expr">quadratic expression for the constraint</param>
        /// <param name="sense">constraint type ( <=, >= , == )</param>
        /// <param name="rhs">constant value on the right side of the constraint</param>
        /// <param name="name">constraint name. If an empty string or null is passed, the constraint name will be generated</param>
        /// <returns>new constraint object</returns>
        /// </summary>
        public Constraint AddConstraint(QuadExpression quad_expr, Constraint_sense sense, double rhs, string name)
        {
            var constr_handle = Model_AddConstraintQuad1(model_, quad_expr.Get(), sense, rhs, name);
            CheckError();
            return new Constraint(constr_handle);
        }

        /// <summary>
        /// \~russian
        /// Добавляет ограничение в модель с заданными параметрами
        /// <param name="lin_expr">линейное выражение для ограничения</param>
        /// <param name="lb">нижняя граница ограничения</param>
        /// <param name="ub">верхняя граница ограничения</param>
        /// <param name="name">имя ограничения. Если передана пустая строка или null, имя ограничения будет сгенерировано</param>
        /// <returns>объект нового ограничения</returns>
        /// 
        /// \~english
        /// Adds a constraint to the model with the given parameters
        /// <param name="lin_expr">linear expression for the constraint</param>
        /// <param name="lb">lower bound of the constraint</param>
        /// <param name="ub">upper bound of the constraint</param>
        /// <param name="name">constraint name. If an empty string or null is passed, the constraint name will be generated</param>
        /// <returns>new constraint object</returns>
        /// 
        /// \~chinese-traditional
        /// Adds a constraint to the model with the given parameters
        /// <param name="lin_expr">linear expression for the constraint</param>
        /// <param name="lb">lower bound of the constraint</param>
        /// <param name="ub">upper bound of the constraint</param>
        /// <param name="name">constraint name. If an empty string or null is passed, the constraint name will be generated</param>
        /// <returns>new constraint object</returns>
        /// </summary>
        public Constraint AddConstraint(LinearExpression lin_expr, double lb, double ub, string name)
        {
            var constr_handle = Model_AddConstraintLinear2(model_, lin_expr.Get(), lb, ub, name);
            CheckError();
            return new Constraint(constr_handle);
        }

        /// <summary>
        /// \~russian
        /// Добавляет ограничение в модель с заданными параметрами
        /// <param name="quad_expr">квадратичное выражение для ограничения</param>
        /// <param name="lb">нижняя граница ограничения</param>
        /// <param name="ub">верхняя граница ограничения</param>
        /// <param name="name">имя ограничения. Если передана пустая строка или null, имя ограничения будет сгенерировано</param>
        /// <returns>объект нового ограничения</returns>
        /// 
        /// \~english
        /// Adds a constraint to the model with the given parameters
        /// <param name="quad_expr">quadratic expression for the constraint</param>
        /// <param name="lb">lower bound of the constraint</param>
        /// <param name="ub">upper bound of the constraint</param>
        /// <param name="name">constraint name. If an empty string or null is passed, the constraint name will be generated</param>
        /// <returns>new constraint object</returns>
        /// 
        /// \~chinese-traditional
        /// Adds a constraint to the model with the given parameters
        /// <param name="quad_expr">quadratic expression for the constraint</param>
        /// <param name="lb">lower bound of the constraint</param>
        /// <param name="ub">upper bound of the constraint</param>
        /// <param name="name">constraint name. If an empty string or null is passed, the constraint name will be generated</param>
        /// <returns>new constraint object</returns>
        /// </summary>
        public Constraint AddConstraint(QuadExpression quad_expr, double lb, double ub, string name)
        {
            var constr_handle = Model_AddConstraintQuad2(model_, quad_expr.Get(), lb, ub, name);
            CheckError();
            return new Constraint(constr_handle);
        }

        /// <summary>
        /// \~russian
        /// Добавляет уже существующее ограничение в модель
        /// <param name="constr">ограничение</param>
        /// <param name="name">имя ограничения</param>
        /// <returns>объект нового ограничения</returns>
        /// 
        /// \~english
        /// Adds an existing constraint to the model
        /// <param name="constr">constraint</param>
        /// <param name="name">constraint name</param>
        /// <returns>new constraint object</returns>
        /// 
        /// \~chinese-traditional
        /// Adds an existing constraint to the model
        /// <param name="constr">constraint</param>
        /// <param name="name">constraint name</param>
        /// <returns>new constraint object</returns>
        /// </summary>
        public Constraint AddConstraint(Constraint constr, string name)
        {
            var constr_handle = Model_AddConstraint(model_, constr.Get(), name);
            CheckError();
            return new Constraint(constr_handle);
        }

        /// <summary>
        /// \~russian
        /// Получает количество переменных в модели
        /// <returns>количество переменных в модели</returns>
        /// 
        /// \~english
        /// Gets the number of variables in the model
        /// <returns>the number of variables in the model</returns>
        /// 
        /// \~chinese-traditional
        /// Gets the number of variables in the model
        /// <returns>the number of variables in the model</returns>
        /// </summary>
        public int GetVariablesCount()
        {
            var result = Model_GetVariablesCount(model_);
            CheckError();
            return result;
        }

        /// <summary>
        /// \~russian
        /// Получает переменную модели по индексу
        /// <param name="var_idx">индекс переменной</param>
        /// <returns>объект переменной</returns>
        /// 
        /// \~english
        /// Gets a model variable by index
        /// <param name="var_idx">variable index</param>
        /// <returns>variable object</returns>
        /// 
        /// \~chinese-traditional
        /// Gets a model variable by index
        /// <param name="var_idx">variable index</param>
        /// <returns>variable object</returns>
        /// </summary>
        public Variable GetVariable(int var_idx)
        {
            var var_handle = Model_GetVariable(model_, var_idx);
            CheckError();
            return new Variable(var_handle);
        }

        /// <summary>
        /// \~russian
        /// Получает переменную модели по имени
        /// <param name="name">имя переменной</param>
        /// <returns>объект переменной</returns>
        /// 
        /// \~english
        /// Gets a model variable by name
        /// <param name="name">variable name</param>
        /// <returns>variable object</returns>
        /// 
        /// \~chinese-traditional
        /// Gets a model variable by name
        /// <param name="name">variable name</param>
        /// <returns>variable object</returns>
        /// </summary>
        public Variable GetVariable(string name)
        {
            var var_handle = Model_GetVariableByName(model_, name);
            CheckError();
            return new Variable(var_handle);
        }

        /// <summary>
        /// \~russian
        /// Получает количество ограничений модели
        /// <returns>количество ограничений модели</returns>
        /// 
        /// \~english
        /// Gets the number of model constraints
        /// <returns>the number of model constraints</returns>
        /// 
        /// \~chinese-traditional
        /// Gets the number of model constraints
        /// <returns>the number of model constraints</returns>
        /// </summary>
        public int GetConstraintsCount()
        {
            var res = Model_GetConstraintsCount(model_);
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Получает ограничение из модели по индексу
        /// <param name="constr_idx">индекс ограничения</param>
        /// <returns>объект ограничения</returns>
        /// 
        /// \~english
        /// Gets a constraint from the model by index
        /// <param name="constr_idx">constraint index</param>
        /// <returns>constraint object</returns>
        /// 
        /// \~chinese-traditional
        /// Gets a constraint from the model by index
        /// <param name="constr_idx">constraint index</param>
        /// <returns>constraint object</returns>
        /// </summary>
        public Constraint GetConstraint(int constr_idx)
        {
            var constr_handle = Model_GetConstraint(model_, constr_idx);
            CheckError();
            return new Constraint(constr_handle);
        }

        /// <summary>
        /// \~russian
        /// Получает ограничение из модели по имени
        /// <param name="name">имя ограничения</param>
        /// <returns>объект ограничения</returns>
        /// 
        /// \~english
        /// Gets a constraint from the model by name
        /// <param name="name">constraint name</param>
        /// <returns>constraint object</returns>
        /// 
        /// \~chinese-traditional
        /// Gets a constraint from the model by name
        /// <param name="name">constraint name</param>
        /// <returns>constraint object</returns>
        /// </summary>
        public Constraint GetConstraint(string name)
        {
            var constr_handle = Model_GetConstraintByName(model_, name);
            CheckError();
            return new Constraint(constr_handle);
        }

        /// <summary>
        /// \~russian
        /// Задаёт тип оптимизации целевой функции - максимизация или минимизация
        /// <param name="sense">тип оптимизации</param>
        /// 
        /// \~english
        /// Specifies the type of optimization of the objective function - maximization and minimization
        /// <param name="sense">optimization type</param>
        /// 
        /// \~chinese-traditional
        /// Specifies the type of optimization of the objective function - maximization and minimization
        /// <param name="sense">optimization type</param>
        /// </summary>
        public void SetObjectiveSense(Objective_sense sense)
        {
            Model_SetObjectiveSense(model_, sense);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Получает тип оптимизации целевой функции
        /// <returns>тип оптимизации целевой функции</returns>
        /// 
        /// \~english
        /// Gets the optimization type of the objective function
        /// <returns>type of objective function optimization</returns>
        /// 
        /// \~chinese-traditional
        /// Gets the optimization type of the objective function
        /// <returns>type of objective function optimization</returns>
        /// </summary>
        public Objective_sense GetObjectiveSense()
        {
            var res =  Model_GetObjectiveSense(model_);
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Задаёт константу в выражении целевой функции
        /// <param name="offset">значение константы в целевой функции</param>
        /// 
        /// \~english
        /// Specifies a constant in the objective function expression
        /// <param name="offset">the value of the constant in the objective function</param>
        /// 
        /// \~chinese-traditional
        /// Specifies a constant in the objective function expression
        /// <param name="offset">the value of the constant in the objective function</param>
        /// </summary>
        public void SetObjectiveOffset( double offset)
        {
            Model_SetObjectiveOffset(model_, offset);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Задаёт выражение целевой функции и тип оптимизации
        /// <param name="expr">линейное выражение</param>
        /// <param name="sense">тип оптимизации</param>
        /// 
        /// \~english
        /// Specifies the objective function expression and optimization type
        /// <param name="expr">linear expression</param>
        /// <param name="sense">optimization type</param>
        /// 
        /// \~chinese-traditional
        /// Specifies the objective function expression and optimization type
        /// <param name="expr">linear expression</param>
        /// <param name="sense">optimization type</param>
        /// </summary>
        public void SetObjective(LinearExpression expr, Objective_sense sense = Objective_sense.minimize)
        {
            Model_SetObjectiveLinear(model_, expr.Get(), sense);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Задаёт выражение целевой функции и тип оптимизации
        /// <param name="quad_expr">квадратичное выражение</param>
        /// <param name="sense">тип оптимизации</param>
        /// 
        /// \~english
        /// Specifies the objective function expression and optimization type
        /// <param name="quad_expr">quadratic expression</param>
        /// <param name="sense">optimization type</param>
        /// 
        /// \~chinese-traditional
        /// Specifies the objective function expression and optimization type
        /// <param name="quad_expr">quadratic expression</param>
        /// <param name="sense">optimization type</param>
        /// </summary>
        public void SetObjective(QuadExpression quad_expr, Objective_sense sense = Objective_sense.minimize)
        {
            Model_SetObjectiveQuad(model_, quad_expr.Get(), sense);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Удаляет целевую функцию из модели
        /// 
        /// \~english
        /// Removes the objective function from the model
        /// 
        /// \~chinese-traditional
        /// Removes the objective function from the model
        /// </summary>
        public void ClearObjective()
        {
            Model_ClearObjective(model_);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Получает выражение целевой функции
        /// <returns>Объект выражения</returns>
        /// 
        /// \~english
        /// Gets the objective function expression
        /// <returns>Expression object</returns>
        /// 
        /// \~chinese-traditional
        /// Gets the objective function expression
        /// <returns>Expression object</returns>
        /// </summary>
        public LinearExpression GetObjective()
        {
            var expr_handle = Model_GetObjective(model_);
            CheckError();
            return LinearExpression.CreateFromHandle(expr_handle);
        }

        /// <summary>
        /// \~russian
        /// Получает выражение целевой функции
        /// <returns>Объект выражения</returns>
        /// 
        /// \~english
        /// Gets the objective function expression
        /// <returns>Expression object</returns>
        /// 
        /// \~chinese-traditional
        /// Gets the objective function expression
        /// <returns>Expression object</returns>
        /// </summary>
        public QuadExpression GetQuadObjective()
        {
            var expr_handle = Model_GetQuadObjective(model_);
            CheckError();
            return QuadExpression.CreateFromHandle(expr_handle);
        }
    
        /// <summary>
        /// \~russian
        /// Стартует оптимизацию модели
        /// <returns>Объект с результатом оптимизации</returns>        
        /// 
        /// \~english
        /// Starts model optimization
        /// <returns>Object with optimization result</returns>
        /// 
        /// \~chinese-traditional
        /// Starts model optimization
        /// <returns>Object with optimization result</returns>
        /// </summary>
        public SolveResult Solve()
        {
            var res = Model_Solve(model_);
            CheckError();
            return SolveResult.CreateFromHandle(res);
        }
  
        /// <summary>
        /// \~russian
        /// Стартует оптимизацию модели на удаленном сервере в синхронном режиме.
        /// Предварительно необходимо установить переменную окружения X_API_KEY.
        /// <param name="name_mapping_file">путь к файлу, который будет записан
        /// и будет содержать соответствие оригинальных имен модели и анонимизированных</param>
        /// <returns>Объект с результатом оптимизации</returns>            
        /// 
        /// \~english
        /// Starts model optimization on a remote server in synchronous mode.
        /// You must first set the environment variable X_API_KEY.
        /// <param name="name_mapping_file">path to the file that will be written
        /// and will contain the correspondence between the original names of the model and the anonymized</param>
        /// <returns>Object with the optimization result</returns>     
        /// 
        /// \~chinese-traditional
        /// Starts model optimization on a remote server in synchronous mode.
        /// You must first set the environment variable X_API_KEY.
        /// <param name="name_mapping_file">path to the file that will be written
        /// and will contain the correspondence between the original names of the model and the anonymized</param>
        /// <returns>Object with the optimization result</returns>     
        /// </summary>
        public SolveResult SolveRemote(string name_mapping_file)
        {
            var res = Model_SolveRemote(model_, name_mapping_file);
            CheckError();
            return SolveResult.CreateFromHandle(res);
        }

        /// <summary>
        /// \~russian
        /// Стартует оптимизацию модели на удаленном сервере в асинхронном
        /// режиме без ожидания результата. Предварительно необходимо
        /// установить переменную окружения X_API_KEY.
        /// <param name="name_mapping_file">путь к файлу, который будет записан
        /// и будет содержать соответствие оригинальных имен модели и анонимизированных</param>
        /// 
        /// \~english
        /// Starts model optimization on a remote server in asynchronous
        /// mode without waiting for the result. You must first
        /// set the environment variable X_API_KEY.
        /// <param name="name_mapping_file">path to the file that will be written
        /// and will contain the correspondence between the original names of the model and the anonymized</param>
        /// 
        /// \~chinese-traditional
        /// Starts model optimization on a remote server in asynchronous
        /// mode without waiting for the result. You must first
        /// set the environment variable X_API_KEY.
        /// <param name="name_mapping_file">path to the file that will be written
        /// and will contain the correspondence between the original names of the model and the anonymized</param>
        /// </summary>
        public void SolveRemoteAsync(string name_mapping_file)
        {
            Model_SolveRemoteAsync(model_, name_mapping_file);
            CheckError();
        }
    
        /// <summary>
        /// \~russian
        /// Получает лог удалённого расчета
        /// <param name="calc_uid">идентификатор удалённого расчета</param>
        /// <returns>лог удалённого расчета</returns>     
        /// 
        /// \~english
        /// Gets the remote calculation log
        /// <param name="calc_uid">remote calculation identifier</param>
        /// <returns>remote calculation log</returns>     
        /// 
        /// \~chinese-traditional
        /// Gets the remote calculation log
        /// <param name="calc_uid">remote calculation identifier</param>
        /// <returns>remote calculation log</returns>    
        /// </summary>
        public string? GetRemoteSolveLog(string calc_uid)
        {
            IntPtr log = Model_GetRemoteSolveLog(model_, calc_uid);
            CheckError();
            if (log == IntPtr.Zero)
            {
                return "";
            }

            return StringConverter.StringFromArhiplex(log);
        }

        /// <summary>
        /// \~russian
        /// Удаляет переменную из модели
        /// <param name="var">переменная</param>
        /// 
        /// \~english
        /// Removes a variable from the model
        /// <param name="var">variable</param>
        /// 
        /// \~chinese-traditional
        /// Removes a variable from the model
        /// <param name="var">variable</param>
        /// </summary>
        public void Remove(Variable var)
        {
            Model_RemoveVar(model_, var.Get());
            CheckError();
        }
  
        /// <summary>
        /// \~russian
        /// Удаляет ограничение из модели
        /// <param name="constr">ограничение</param>     
        /// 
        /// \~english
        /// Removes a constraint from the model
        /// <param name="constr">constraint</param>     
        /// 
        /// \~chinese-traditional
        /// Removes a constraint from the model
        /// <param name="constr">constraint</param>     
        /// </summary>  
        public void Remove(Constraint constr)
        {
            Model_RemoveConstr(model_, constr.Get());
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Очищает модель от всех данных. Все старые переменные и ограничения становятся невалидными
        /// 
        /// \~english
        /// Clears the model of all data. All old variables and constraints become invalid
        /// 
        /// \~chinese-traditional
        /// Clears the model of all data. All old variables and constraints become invalid
        /// </summary>  
        public void Clear()
        {
            Model_Clear(model_);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Задать файл для записи лога решения
        /// <param name="log_file">файл лога</param> 
        /// 
        /// \~english
        /// Set file for writing solution log
        /// <param name="log_file">log file</param>   
        /// 
        /// \~chinese-traditional
        /// Set file for writing solution log
        /// <param name="log_file">log file</param>   
        /// </summary>  
        public void SetLogFile( string log_file)
        {
            Model_SetLogFile(model_, log_file);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Добавить возможное значение переменной в решении в качестве "подсказки". 
        /// Стартовые значения очищаются после начала процесса решения.
        /// <param name="var_name">имя переменной</param>
        /// <param name="var_value">значение переменной</param>
        /// 
        /// \~english
        /// Add a possible value of a variable to the solution as a "hint".
        /// Starting values are cleared after the solution process begins.
        /// <param name="var_name">variable name</param>
        /// <param name="var_value">variable value</param> 
        /// 
        /// \~chinese-traditional
        /// Add a possible value of a variable to the solution as a "hint".
        /// Starting values are cleared after the solution process begins.
        /// <param name="var_name">variable name</param>
        /// <param name="var_value">variable value</param> 
        /// </summary>  
        public void AddMipStartValue( string var_name, double var_value)
        {
            Model_AddMipStartValueByName(model_, var_name, var_value);
            CheckError();
        }
   
        /// <summary>
        /// \~russian
        /// Добавить возможное значение переменной в решении в качестве "подсказки". 
        /// Стартовые значения очищаются после начала процесса решения.
        /// <param name="var">переменная</param>
        /// <param name="var_value">значение переменной</param>
        /// 
        /// \~english
        /// Add a possible value of a variable to the solution as a "hint".
        /// Starting values are cleared after the solution process begins.
        /// <param name="var">variable</param>
        /// <param name="var_value">variable value</param> 
        /// 
        /// \~chinese-traditional
        /// Add a possible value of a variable to the solution as a "hint".
        /// Starting values are cleared after the solution process begins.
        /// <param name="var">variable</param>
        /// <param name="var_value">variable value</param> 
        /// </summary>  
        public void AddMipStartValue( Variable var, double var_value)
        {
            Model_AddMipStartValue(model_, var.Get(), var_value);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Добавить возможное значение переменной в решении в качестве "подсказки". 
        /// Стартовые значения очищаются после начала процесса решения.
        /// <param name="sol_file">путь к файлу с решением</param>
        /// 
        /// \~english
        /// Add a possible value of a variable to the solution as a "hint".
        /// Starting values are cleared after the solution process begins.
        /// <param name="sol_file">path to the file with the solution</param>
        /// 
        /// \~chinese-traditional
        /// Add a possible value of a variable to the solution as a "hint".
        /// Starting values are cleared after the solution process begins.
        /// <param name="sol_file">path to the file with the solution</param>
        /// </summary>  
        public void AddMipStartValues(string sol_file)
        {
            Model_AddMipStartValues(model_, sol_file);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Удалить все стартовые значения для поиска решения
        /// 
        /// \~english
        /// Remove all starting values to find a solution
        /// 
        /// \~chinese-traditional
        /// Remove all starting values to find a solution
        /// </summary>  
        public void ClearMipStartValues()
        {
            Model_ClearMipStartValues(model_);
            CheckError();
        }
    
        /// <summary>
        /// \~russian
        /// Получить уникальный идентификатор последнего удаленного расчета.
        /// <returns>идентификатор удаленного расчета.</returns>        
        /// 
        /// \~english
        /// Get the unique identifier of the last remote calculation.
        /// <returns>remote calculation identifier.</returns>        
        /// 
        /// \~chinese-traditional
        /// Get the unique identifier of the last remote calculation.
        /// <returns>remote calculation identifier.</returns>        
        /// </summary>  
        public string? GetCalcUID()
        {
            IntPtr calc_uid = Model_GetCalcUID(model_);
            CheckError();
            return StringConverter.StringFromArhiplex(calc_uid);
        }

        /// <summary>
        /// \~russian
        /// Проверяет является ли задача целочисленной.
        /// <returns>true если задача целочисленная, иначе false.</returns>        
        /// 
        /// \~english
        /// Checks if the problem is an integer.
        /// <returns>true if the problem is an integer, otherwise false.</returns>        
        /// 
        /// \~chinese-traditional
        /// Checks if the problem is an integer.
        /// <returns>true if the problem is an integer, otherwise false.</returns>        
        /// </summary>  
        public bool IsMip()
        {
            return Model_IsMip(model_) != 0 ;
        }

        /// <summary>
        /// \~russian
        /// Проверяет является ли задача нелинейной.
        /// <returns>true если задача нелинейная, иначе false.</returns>        
        /// 
        /// \~english
        /// Checks whether the problem is nonlinear.
        /// <returns>true if the problem is nonlinear, otherwise false.</returns>        
        /// 
        /// \~chinese-traditional
        /// Checks whether the problem is nonlinear.
        /// <returns>true if the problem is nonlinear, otherwise false.</returns>        
        /// </summary>  
        public bool IsNonlinear()
        {
            return Model_IsNonlinear(model_) != 0;
        }

        private IntPtr model_;
    }

    /// <summary>
    /// \~russian
    /// Класс для работа с переменной - чтение/изменение имени, верхней и 
    /// нижней границы, типа, а также удаление
    /// 
    /// \~english
    /// Class for working with a variable - reading/changing the name, upper and lower
    /// bounds, type, and also deleting
    /// 
    /// \~chinese-traditional
    /// Class for working with a variable - reading/changing the name, upper and lower
    /// bounds, type, and also deleting
    /// </summary>
    public class Variable : IDisposable
    {
        #region arhiplex_imports
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr Variable_GetName(IntPtr var);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Variable_SetName(IntPtr var, string name);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Variable_Release(IntPtr var);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern int Variable_GetErrorCode(IntPtr var);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr Variable_GetErrorText(IntPtr var);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Variable_SetUpperBound(IntPtr var, double upper_bound);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern double Variable_GetUpperBound(IntPtr var);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Variable_SetLowerBound(IntPtr var, double lower_bound);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern double Variable_GetLowerBound(IntPtr var);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Variable_Remove(IntPtr var);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern Variable_type Variable_GetType(IntPtr var);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Variable_SetType(IntPtr var, Variable_type type);
        #endregion

        public override string ToString()
        {
            return GetName()+" : "+GetVarType();
        }

        public override bool Equals(object? obj)
        {
            return (this as object).Equals(obj );
        }
        public override int GetHashCode()
        {
            return (this as object).GetHashCode();
        }
        private void CheckError()
        {
            if (GetErrorCode() != 0)
            {
                throw new ArhiplexException(GetErrorCode(), GetErrorText() ?? "");
            }
        }
        private int GetErrorCode()
        {
            return Variable_GetErrorCode(variable_);
        }
        
        private string? GetErrorText()
        {
            IntPtr text = Variable_GetErrorText(variable_);
            return StringConverter.StringFromArhiplex(text);
        }

        /// <summary>
        /// \~russian
        /// Задаёт верхнюю границу для переменной
        /// <param name="upper_bound">новая верхняя граница переменной</param>     
        /// 
        /// \~english
        /// Sets the upper bound for the variable
        /// <param name="upper_bound">new upper bound for the variable</param>
        /// 
        /// \~chinese-traditional
        /// Sets the upper bound for the variable
        /// <param name="upper_bound">new upper bound for the variable</param>
        /// </summary>
        public void SetUpperBound(double upper_bound)
        {
            Variable_SetUpperBound(variable_, upper_bound);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Получает верхнюю границу переменной
        /// <returns>верхняя граница переменной</returns>  
        /// 
        /// \~english
        /// Gets the upper bound of the variable
        /// <returns>the upper bound of the variable</returns>  
        /// 
        /// \~chinese-traditional
        /// Gets the upper bound of the variable
        /// <returns>the upper bound of the variable</returns>  
        /// </summary>      
        public double GetUpperBound()
        {
            var res = Variable_GetUpperBound(variable_);
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Задаёт нижнюю границу для переменной
        /// <param name="lower_bound">новая нижняя граница переменной</param>   
        /// 
        /// \~english
        /// Sets the lower bound for the variable
        /// <param name="lower_bound">new lower bound for the variable</param>
        /// 
        /// \~chinese-traditional
        /// Sets the lower bound for the variable
        /// <param name="lower_bound">new lower bound for the variable</param>
        /// </summary>
        public void SetLowerBound(double lower_bound)
        {
            Variable_SetUpperBound(variable_, lower_bound);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Получает нижнюю границу переменной
        /// <returns>нижняя граница переменной</returns>  
        /// 
        /// \~english
        /// Gets the lower bound of a variable
        /// <returns>the lower bound of a variable</returns>  
        /// 
        /// \~chinese-traditional
        /// Gets the lower bound of a variable
        /// <returns>the lower bound of a variable</returns>  
        /// </summary>      
        public double GetLowerBound()
        {
            var res = Variable_GetLowerBound(variable_);
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Помечает переменную как удаленную. Переменная не будет участвовать в расчетах
        /// 
        /// \~english
        /// Marks the variable as removed. The variable will not participate in calculations.
        /// 
        /// \~chinese-traditional
        /// Marks the variable as removed. The variable will not participate in calculations.
        /// </summary>   
        public void Remove()
        {
            Variable_Remove(variable_);
            CheckError();
        }
 
        /// <summary>
        /// \~russian
        /// Получает тип переменной
        /// <returns>тип переменной</returns>        
        /// 
        /// \~english
        /// Gets the type of the variable
        /// <returns>the type of the variable</returns>  
        /// 
        /// \~chinese-traditional
        /// Gets the type of the variable
        /// <returns>the type of the variable</returns>  
        /// </summary>      
        public Variable_type GetVarType()
        {
            var res = Variable_GetType(variable_);
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Задает тип переменной в модели
        /// <param name="type">тип переменной</param>
        /// 
        /// \~english
        /// Sets the type of variable in the model
        /// <param name="type">variable type</param>
        /// 
        /// \~chinese-traditional
        /// Sets the type of variable in the model
        /// <param name="type">variable type</param>
        /// </summary>
        public void SetType(Variable_type type)
        {
            Variable_SetType(variable_, type);
            CheckError();
        }

        public static LinearExpression operator +(Variable var, double a)
        {
            var tmp = new LinearExpression(var, 1.0);
            tmp.SetConstant(a);
            return tmp;
        }

        public static LinearExpression operator +(double a, Variable var)=> var + a;
        public static LinearExpression operator +(Variable var1, Variable var2)
        {
            var tmp = new LinearExpression(var1);
            tmp.AddTerm(var2, 1.0);
            return tmp;
        }
        public static LinearExpression operator +(LinearExpression lin, Variable var)
        {
            var tmp = lin.CreateFreeCopy();
            tmp.AddTerm(var, 1.0);
            return tmp;
        }

        public static LinearExpression operator -(Variable var)=> new LinearExpression(var, -1.0);
        
        public static LinearExpression operator +(Variable var, LinearExpression lin) => lin + var;

        public static LinearExpression operator -(LinearExpression lin, Variable var)
        {
            var tmp = lin.CreateFreeCopy();
            tmp.AddTerm(var, -1.0);
            return tmp;
        }

        public static LinearExpression operator -(Variable var, LinearExpression lin)
        {
            var tmp = new LinearExpression(var, 1.0);
            tmp.AddExpression(lin, -1.0);
            return tmp;
        }

        public static LinearExpression operator -(Variable var1, Variable var2)
        {
            var tmp = new LinearExpression(var1);
            tmp.AddTerm(var2, -1.0);
            return tmp;
        }
        public static LinearExpression operator -(double a, Variable var)
        {
            var tmp = new LinearExpression(var, -1.0);
            tmp.SetConstant(a);
            return tmp;
        }

        public static LinearExpression operator -(Variable var, double a)
        {
            var tmp = new LinearExpression(var, 1.0);
            tmp.SetConstant(-a);
            return tmp;
        }

        public static QuadExpression operator *(Variable var1, Variable var2) => new QuadExpression(var1, var2, 1.0);
        public static LinearExpression operator *(Variable var, double a) => new LinearExpression(var, a);
        public static LinearExpression operator /(Variable var, double a) => new LinearExpression(var, 1/a);

        public static LinearExpression operator *(double a, Variable var) => var * a;

        public static Constraint operator ==(double a, Variable right)
        {
            return new Constraint(new LinearExpression(right), Constraint_sense.equal, a);
        }
        public static Constraint operator >=(double a, Variable right)
        {
            return new Constraint(new LinearExpression(right), Constraint_sense.less_equal, a);
        }

        public static Constraint operator >=(Variable right, double a)
        {
            return new Constraint(new LinearExpression(right), Constraint_sense.greater_equal, a);
        }

        public static Constraint operator <=(Variable right, double a)
        {
            return new Constraint(new LinearExpression(right), Constraint_sense.less_equal, a);
        }

        public static Constraint operator <=(double a, Variable right)
        {
            return new Constraint(new LinearExpression(right), Constraint_sense.greater_equal, a);
        }


        public static Constraint operator !=(double a, Variable right)
        {
            throw new NotImplementedException();
        }


        private IntPtr variable_;

        
        public Variable(IntPtr variable)
        {
            variable_ = variable;
        }
        public IntPtr Get()
        { 
            return variable_;
        }
 
        /// <summary>
        /// \~russian
        /// Получает имя переменной
        /// <returns>имя переменной</returns>        
        /// 
        /// \~english
        /// Gets the name of a variable
        /// <returns>the name of the variable</returns>  
        /// 
        /// \~chinese-traditional
        /// Gets the name of a variable
        /// <returns>the name of the variable</returns>  
        /// </summary>      
        public string? GetName()
        {
            IntPtr name = Variable_GetName(variable_);
            CheckError();
            return StringConverter.StringFromArhiplex(name);
        }

        /// <summary>
        /// \~russian
        /// Задаёт имя переменной в модели
        /// <param name="name">имя переменной</param>
        /// 
        /// \~english
        /// Sets the name of the variable in the model
        /// <param name="name">variable name</param>
        /// 
        /// \~chinese-traditional
        /// Sets the name of the variable in the model
        /// <param name="name">variable name</param>
        /// </summary>
        public void SetName(string name)
        {
            Variable_SetName(variable_, name);
            CheckError();
        }

        private void Cleanup()
        {
            if (variable_ != IntPtr.Zero)
            {
                Variable_Release(variable_);
                variable_ = IntPtr.Zero;
            }
        }

        public void Dispose()
        {
            Cleanup();

            // Prevent the object from being placed on the
            // finalization queue
            System.GC.SuppressFinalize(this);
        }

        ~Variable()
        {
            Cleanup();
        }
    }

    /// <summary>
    /// \~russian
    /// Работа с ограничениями у модели, позволяет работать с выражением и границами
    /// 
    /// \~english
    /// Working with model constraints, allows you to work with expression and boundaries
    /// 
    /// \~chinese-traditional
    /// Working with model constraints, allows you to work with expression and boundaries
    /// </summary>
    public class Constraint : IDisposable
    {
        private IntPtr constraint_;

        #region arhiplex_imports
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]

        private static extern IntPtr Constraint_GetName(IntPtr constr);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void Constraint_SetName(IntPtr constr, string name);
        
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern IntPtr Constraint_CreateFromLinear(IntPtr linear_expr, arhiplex.Constraint_sense sense, double coef);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern IntPtr Constraint_CreateFromQuad(IntPtr quad_expr, arhiplex.Constraint_sense sense, double coef);


        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern void Constraint_Remove(IntPtr constr);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern IntPtr Constraint_GetLinearExpression(IntPtr constr);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern IntPtr Constraint_GetQuadExpression(IntPtr constr);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern void Constraint_SetUpperBound(IntPtr constr, double upper_bound);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern double Constraint_GetUpperBound(IntPtr constr);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern void Constraint_SetLowerBound(IntPtr constr, double lower_bound);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern double Constraint_GetLowerBound(IntPtr constr);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern double Constraint_GetRange(IntPtr constr);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern void Constraint_SetRange(IntPtr constr, double range);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern void Constraint_Release(IntPtr constr);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern int Constraint_GetErrorCode(IntPtr constr);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern IntPtr Constraint_GetErrorText(IntPtr constr);
        #endregion
        ~Constraint()
        {
            Cleanup();
        }
        public Constraint(IntPtr constraint)
        {
            constraint_ = constraint;
        }

        public Constraint(LinearExpression linear, arhiplex.Constraint_sense sense , double coef)
        {
            constraint_ = Constraint_CreateFromLinear(linear.Get(), sense, coef);
        }

        public Constraint(QuadExpression quad, arhiplex.Constraint_sense sense, double coef)
        {
            constraint_ = Constraint_CreateFromQuad(quad.Get(), sense, coef);
        }
        public IntPtr Get()
        {
            return constraint_;
        }

        /// <summary>
        /// \~russian
        /// Получает имя ограничения
        /// <returns>имя ограничения</returns>        
        /// 
        /// \~english
        /// Gets the constraint name
        /// <returns>the constraint name</returns>    
        /// 
        /// \~chinese-traditional
        /// Gets the constraint name
        /// <returns>the constraint name</returns>    
        /// </summary>
        public string? GetName()
        {
            IntPtr name = Constraint_GetName(constraint_);
            CheckError();
            return StringConverter.StringFromArhiplex(name);
        }

        /// <summary>
        /// \~russian
        /// Задаёт имя ограничения в модели
        /// <param name="name">имя ограничение</param>      
        /// 
        /// \~english
        /// Sets the name of the constraint in the model
        /// <param name="name">constraint name</param>
        /// 
        /// \~chinese-traditional
        /// Sets the name of the constraint in the model
        /// <param name="name">constraint name</param>
        /// </summary>
        public void SetName(string name)
        {
            Constraint_SetName(constraint_, name);
            CheckError();
        }

        private void Cleanup()
        {
            if (constraint_ != IntPtr.Zero)
            {
                Constraint_Release(constraint_);
                constraint_ = IntPtr.Zero;
            }
        }

        public void Dispose()
        {
            Cleanup();

            // Prevent the object from being placed on the
            // finalization queue
            System.GC.SuppressFinalize(this);
        }
        public override string ToString()
        {
            string result = GetLinearExpression().ToString();
            double ub = GetUpperBound();
            double lb = GetLowerBound();
            string name = GetName() ?? "";
            if(ub != Model.Inf)
            {
                result = result + " <= " + ub;
            }

            if (lb != -Model.Inf)
            {
                result = lb + " <= " + result;
            }
            if( name.Length > 0 )
            {
                result = name + ": " + result;
            }
            return result;
        }

        private void CheckError()
        {
            if (GetErrorCode() != 0)
            {
                throw new ArhiplexException(GetErrorCode(), GetErrorText() ?? "");
            }
        }

        /// <summary>
        /// \~russian
        /// Помечает ограничение как удаленное. Такое ограничение будет исключено из расчетов    
        /// 
        /// \~english
        /// Marks the constraint as removed. This constraint will be excluded from calculations.
        /// 
        /// \~chinese-traditional
        /// Marks the constraint as removed. This constraint will be excluded from calculations.
        /// </summary>
        public void Remove()
        {
            Constraint_Remove(constraint_);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Линейное выражение, связанное с ограничением
        /// <returns>Линейное выражение</returns>        
        /// 
        /// \~english
        /// Linear expression associated with constraint
        /// <returns>Linear expression</returns>
        /// 
        /// \~chinese-traditional
        /// Linear expression associated with constraint
        /// <returns>Linear expression</returns>
        /// </summary>
        public LinearExpression GetLinearExpression()
        {
            var expr_handle = Constraint_GetLinearExpression(constraint_);
            CheckError();
            return LinearExpression.CreateFromHandle(expr_handle);
        }

        /// <summary>
        /// \~russian
        /// Квадратичное выражение, связанное с ограничением
        /// <returns>Квадратичное выражение</returns>        
        /// 
        /// \~english
        /// Quadratic expression associated with constraint
        /// <returns>Quadratic expression</returns>
        /// 
        /// \~chinese-traditional
        /// Quadratic expression associated with constraint
        /// <returns>Quadratic expression</returns>
        /// </summary>
        public QuadExpression GetQuadExpression()
        {
            var expr_handle = Constraint_GetQuadExpression(constraint_);
            CheckError();
            return QuadExpression.CreateFromHandle(expr_handle);
        }

        /// <summary>
        /// \~russian
        /// Задаёт верхнюю границу для ограничения
        /// <param name="upper_bound">новая верхняя граница ограничения</param> 
        /// 
        /// \~english
        /// Sets the upper bound of the constraint
        /// <param name="upper_bound">the new upper bound of the constraint</param>
        /// 
        /// \~chinese-traditional
        /// Sets the upper bound of the constraint
        /// <param name="upper_bound">the new upper bound of the constraint</param>
        /// </summary>
        public void SetUpperBound(double upper_bound)
        {
            Constraint_SetUpperBound(constraint_, upper_bound);
            CheckError();
        }
     
        /// <summary>
        /// \~russian
        /// Получает верхнюю границу ограничения
        /// <returns>верхняя граница ограничения</returns>        
        /// 
        /// \~english
        /// Gets the upper bound of the constraint
        /// <returns>the upper bound of the constraint</returns>
        /// 
        /// \~chinese-traditional
        /// Gets the upper bound of the constraint
        /// <returns>the upper bound of the constraint</returns>
        /// </summary>
        public double GetUpperBound()
        {
            var res = Constraint_GetUpperBound(constraint_); ;
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Задаёт нижнюю границу для ограничения
        /// <param name="lower_bound">новая нижняя граница ограничения</param>
        /// 
        /// \~english
        /// Sets the lower bound of the constraint
        /// <param name="lower_bound">new lower bound of the constraint</param>
        /// 
        /// \~chinese-traditional
        /// Sets the lower bound of the constraint
        /// <param name="lower_bound">new lower bound of the constraint</param>
        /// </summary>
        public void SetLowerBound(double lower_bound)
        {
            Constraint_SetLowerBound(constraint_, lower_bound);
            CheckError();
        }
      
        /// <summary>
        /// \~russian
        /// Получает нижнюю границу ограничения
        /// <returns>нижняя граница ограничения</returns>    
        /// 
        /// \~english
        /// Gets the lower bound of the constraint
        /// <returns>the lower bound of the constraint</returns>
        /// 
        /// \~chinese-traditional
        /// Gets the lower bound of the constraint
        /// <returns>the lower bound of the constraint</returns>
        /// </summary>
        public double GetLowerBound()
        {
            var res = Constraint_GetLowerBound(constraint_);
            CheckError();
            return res;
        }
   
        /// <summary>
        /// \~russian
        /// Получает интервал ограничения: upper_bound - lower_bound
        /// <returns>upper_bound - lower_bound</returns>        
        /// 
        /// \~english
        /// Gets the constraint range: upper_bound - lower_bound
        /// <returns>upper_bound - lower_bound</returns>        
        /// 
        /// \~chinese-traditional
        /// Gets the constraint range: upper_bound - lower_bound
        /// <returns>upper_bound - lower_bound</returns>        
        /// </summary>
        public double GetRange()
        {
            var res = Constraint_GetRange(constraint_);
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Задаёт интервал ограничения: lower_bound <= expression <= lower_bound + range
        /// <param name="range">интервал для ограничения</param>
        /// 
        /// \~english
        /// Specifies the constraint range: lower_bound <= expression <= lower_bound + range
        /// <param name="range">range for the constraint</param>
        /// 
        /// \~chinese-traditional
        /// Specifies the constraint range: lower_bound <= expression <= lower_bound + range
        /// <param name="range">range for the constraint</param>
        /// </summary>
        public void SetRange(double range)
        {
            Constraint_SetRange(constraint_, range);
            CheckError();
        }
        private int GetErrorCode()
        {
            return Constraint_GetErrorCode(constraint_);
        }

        private string? GetErrorText()
        {
            IntPtr err_text = Constraint_GetErrorText(constraint_);
            return StringConverter.StringFromArhiplex(err_text);
        }
    }

    /// <summary>
    /// \~russian
    /// Обеспечивает работу с линейными выражениями - добавление/удаление элементов и изменение коэффициентов.
    /// Элемент выражения - переменная * коэффициент.
    /// 
    /// \~english
    /// Provides work with linear expressions - adding/removing elements and changing coefficients.
    /// Expression element - variable * coefficient.
    /// 
    /// \~chinese-traditional
    /// Provides work with linear expressions - adding/removing elements and changing coefficients.
    /// Expression element - variable * coefficient.
    /// </summary>
    public class LinearExpression : IDisposable
    {
        private void CheckError()
        {
            if (GetErrorCode() != 0)
            {
                throw new ArhiplexException(GetErrorCode(), GetErrorText() ?? "");
            }
        }

        
        public override string ToString()
        {
            return ExpressionFormatter.FormatExpr(this);
        }
        public override bool Equals(object? obj)
        {
            return (this as object).Equals(obj);
        }
        public override int GetHashCode()
        {
            return (this as object).GetHashCode();
        }

        #region arhiplex_imports

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr LinearExpression_GetName(IntPtr linear_expr);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void LinearExpression_SetName(IntPtr linear_expr, string expr_name);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr LinearExpression_Create();
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void LinearExpression_Release(IntPtr linear_expr);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern int LinearExpression_GetErrorCode(IntPtr linear_expr);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr LinearExpression_GetErrorText(IntPtr linear_expr);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern int LinearExpression_GetTermsCount(IntPtr linear_expr);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr LinearExpression_GetTermVariable(IntPtr linear_expr, int term_idx);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern int LinearExpression_GetTermVariableIndex(IntPtr linear_expr, int term_idx);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern double LinearExpression_GetTermCoeff(IntPtr linear_expr, int term_idx);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void LinearExpression_SetTermCoeff(IntPtr linear_expr, int term_idx, double value);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern double LinearExpression_GetConstant(IntPtr linear_expr);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void LinearExpression_SetConstant(IntPtr linear_expr, double constant);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void LinearExpression_AddConstant(IntPtr linear_expr, double constant);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void LinearExpression_AddTerm(IntPtr linear_expr, IntPtr pVar, double coeff);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void LinearExpression_AddExpression(IntPtr linear_expr, IntPtr pExpr, double mult);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void LinearExpression_RemoveTerm(IntPtr linear_expr, int term_idx);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void LinearExpression_RemoveVariable(IntPtr linear_expr, IntPtr pVar);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr LinearExpression_GetQuadPart(IntPtr linear_expr);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr LinearExpression_CreateFreeCopy(IntPtr linear_expr);
        #endregion
        private IntPtr linear_expr_;
        ~LinearExpression()
        {
            Cleanup();
        }
        private LinearExpression(IntPtr obj_handle)
        {
            linear_expr_ = obj_handle;
        }
        internal static LinearExpression CreateFromHandle(IntPtr obj_handle) => new LinearExpression(obj_handle);
        
        public LinearExpression()
        {
            linear_expr_ = LinearExpression_Create();
        }

        /// <summary>
        /// \~russian
        /// Создает пустое выражение с возможностью задать константу
        /// <param name="offset">константа в выражении</param>
        /// 
        /// \~english
        /// Creates an empty expression with the ability to specify a constant
        /// <param name="offset">constant in the expression</param>
        /// 
        /// \~chinese-traditional
        /// Creates an empty expression with the ability to specify a constant
        /// <param name="offset">constant in the expression</param>
        /// </summary>
        public LinearExpression(double offset)
        {
            linear_expr_ = LinearExpression_Create();
            SetConstant(offset);
        }

        /// <summary>
        /// \~russian
        /// Создает выражение на основе переменной и коэффициента
        /// <param name="var">переменная в выражении</param>
        /// <param name="coeff">коэффициент переменной в выражении</param>
        /// 
        /// \~english
        /// Creates an expression based on a variable and a coefficient
        /// <param name="var">variable in expression</param>
        /// <param name="coeff">coefficient of variable in expression</param>
        /// 
        /// \~chinese-traditional
        /// Creates an expression based on a variable and a coefficient
        /// <param name="var">variable in expression</param>
        /// <param name="coeff">coefficient of variable in expression</param>
        /// </summary>
        public LinearExpression(Variable var, double coeff = 1.0)
        {
            linear_expr_ = LinearExpression_Create();
            AddTerm(var, coeff);
        }

        public IntPtr Get()
        {
            return linear_expr_;
        }
        private void Cleanup()
        {
            if (linear_expr_ != IntPtr.Zero)
            {
                LinearExpression_Release(linear_expr_);
                linear_expr_ = IntPtr.Zero;
            }
        }

        private int GetErrorCode()
        {
            return LinearExpression_GetErrorCode(linear_expr_);
        }

        private string? GetErrorText()
        {
            IntPtr err_text = LinearExpression_GetErrorText(linear_expr_);
            return StringConverter.StringFromArhiplex(err_text);
        }
      
        /// <summary>
        /// \~russian
        /// Возвращает кол-во элементов в выражении
        /// <returns>кол-во элементов в выражении</returns>        
        /// 
        /// \~english
        /// Returns the number of elements in the expression
        /// <returns>number of elements in the expression</returns>  
        /// 
        /// \~chinese-traditional
        /// Returns the number of elements in the expression
        /// <returns>number of elements in the expression</returns>  
        /// </summary>
        public int GetTermsCount()
        {
            var res = LinearExpression_GetTermsCount(linear_expr_);
            CheckError();
            return res;
        }
      
        /// <summary>
        /// \~russian
        /// Возвращает переменную по индексу элемента в выражении
        /// <param name="term_idx">индекс элемента в выражении</param>
        /// <returns>Объект переменной</returns>  
        /// 
        /// \~english
        /// Returns a variable by the index of the element in the expression
        /// <param name="term_idx">index of the element in the expression</param>
        /// <returns>Variable object</returns>  
        /// 
        /// \~chinese-traditional
        /// Returns a variable by the index of the element in the expression
        /// <param name="term_idx">index of the element in the expression</param>
        /// <returns>Variable object</returns>  
        /// </summary>
        public Variable GetTermVariable(int term_idx)
        {
            var var_handle = LinearExpression_GetTermVariable(linear_expr_, term_idx);
            CheckError();
            return new Variable(var_handle);
        }

        /// <summary>
        /// \~russian
        /// Возвращает коэффициент переменной по индексу элемента в выражении
        /// <param name="term_idx">индекс элемента в выражении</param>
        /// <returns>Значение коэффициента переменной</returns>        
        /// 
        /// \~english
        /// Returns the coefficient of the variable by the index of the element in the expression
        /// <param name="term_idx">index of the element in the expression</param>
        /// <returns>Value of the coefficient of the variable</returns>        
        /// 
        /// \~chinese-traditional
        /// Returns the coefficient of the variable by the index of the element in the expression
        /// <param name="term_idx">index of the element in the expression</param>
        /// <returns>Value of the coefficient of the variable</returns>        
        /// </summary>
        public double GetTermCoeff(int term_idx)
        {
            var res = LinearExpression_GetTermCoeff(linear_expr_, term_idx);
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Задает коэффициент переменной по индексу выражения
        /// <param name="term_idx">индекс элемента в выражении</param>
        /// <param name="value">Значение коэффициента при переменной в элементе</param>
        /// 
        /// \~english
        /// Sets the coefficient of the variable by the expression index
        /// <param name="term_idx">index of the element in the expression</param>
        /// <param name="value">Value of the coefficient of the variable in the element</param>
        /// 
        /// \~chinese-traditional
        /// Sets the coefficient of the variable by the expression index
        /// <param name="term_idx">index of the element in the expression</param>
        /// <param name="value">Value of the coefficient of the variable in the element</param>
        /// </summary>
        public void SetTermCoeff(int term_idx, double value)
        {
            LinearExpression_SetTermCoeff(linear_expr_, term_idx, value);
            CheckError ();
        }

        /// <summary>
        /// \~russian
        /// Возвращает константу в выражении
        /// <returns>значение константы</returns> 
        /// 
        /// \~english
        /// Returns the constant in the expression
        /// <returns>the value of the constant</returns> 
        /// 
        /// \~chinese-traditional
        /// Returns the constant in the expression
        /// <returns>the value of the constant</returns> 
        /// </summary>
        public double GetConstant()
        {
            var res = LinearExpression_GetConstant(linear_expr_);
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Задает константу в выражении
        /// <param name="constant">значение константы</param>
        /// 
        /// \~english
        /// Specifies a constant in the expression
        /// <param name="constant">constant value</param>
        /// 
        /// \~chinese-traditional
        /// Specifies a constant in the expression
        /// <param name="constant">constant value</param>
        /// </summary>
        public void SetConstant(double constant)
        {
            LinearExpression_SetConstant(linear_expr_, constant);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Добавляет константу к выражению
        /// <param name="constant">значение константы</param>
        /// 
        /// \~english
        /// Adds a constant to the expression
        /// <param name="constant">constant value</param>
        /// 
        /// \~chinese-traditional
        /// Adds a constant to the expression
        /// <param name="constant">constant value</param>
        /// </summary>
        public void AddConstant(double constant)
        {
            LinearExpression_AddConstant(linear_expr_, constant);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Добавляет элемент к выражению
        /// <param name="var">переменная</param>
        /// <param name="coeff">коэффициент к переменной</param>
        /// 
        /// \~english
        /// Adds an element to the expression
        /// <param name="var">variable</param>
        /// <param name="coeff">coefficient to variable</param>
        /// 
        /// \~chinese-traditional
        /// Adds an element to the expression
        /// <param name="var">variable</param>
        /// <param name="coeff">coefficient to variable</param>
        /// </summary>
        public void AddTerm(Variable var, double coeff)
        {
            LinearExpression_AddTerm(linear_expr_, var.Get(), coeff);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Добавляет выражение к выражению
        /// <param name="expr">добавляемое выражение</param>
        /// <param name="mult">коэффициент к добавляемому выражению</param>
        /// 
        /// \~english
        /// Adds an expression to an expression
        /// <param name="expr">the expression to add</param>
        /// <param name="mult">the coefficient to the expression to add</param>
        /// 
        /// \~chinese-traditional
        /// Adds an expression to an expression
        /// <param name="expr">the expression to add</param>
        /// <param name="mult">the coefficient to the expression to add</param>
        /// </summary>
        public void AddExpression(LinearExpression expr, double mult = 1.0)
        {
            LinearExpression_AddExpression(linear_expr_, expr.Get(), mult);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Удаляет элемент выражения по индексу
        /// <param name="term_idx">индекс удаляемого элемента в выражении</param>
        /// 
        /// \~english
        /// Removes an element of an expression by index
        /// <param name="term_idx">index of the element to remove in the expression</param>
        /// 
        /// \~chinese-traditional
        /// Removes an element of an expression by index
        /// <param name="term_idx">index of the element to remove in the expression</param>
        /// </summary>
        public void RemoveTerm(int term_idx)
        {
            LinearExpression_RemoveTerm(linear_expr_, term_idx);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Удаляет переменную из выражения
        /// <param name="var">удаляемая переменная</param>
        /// 
        /// \~english
        /// Removes a variable from an expression
        /// <param name="var">variable to remove</param>
        /// 
        /// \~chinese-traditional
        /// Removes a variable from an expression
        /// <param name="var">variable to remove</param>
        /// </summary>
        public void RemoveVariable(Variable var)
        {
            LinearExpression_RemoveVariable(linear_expr_, var.Get());
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Возвращает квадратичную часть выражения
        /// <returns>квадратичная часть выражения</returns>        
        /// 
        /// \~english
        /// Returns the quadratic part of the expression
        /// <returns>quadratic part of the expression</returns>
        /// 
        /// \~chinese-traditional
        /// Returns the quadratic part of the expression
        /// <returns>quadratic part of the expression</returns>
        /// </summary>
        public QuadExpression GetQuadPart()
        {
            var expr_handle = LinearExpression_GetQuadPart(linear_expr_);
            CheckError();
            return QuadExpression.CreateFromHandle(expr_handle); 
        }

        /// <summary>
        /// \~russian
        /// Создает копию выражения, не привязанную к модели или другому ограничению
        /// <returns>копия выражения</returns>        
        /// 
        /// \~english
        /// Creates a copy of the expression that is not bound to a model or other constraint
        /// <returns>copy of the expression</returns>
        /// 
        /// \~chinese-traditional
        /// Creates a copy of the expression that is not bound to a model or other constraint
        /// <returns>copy of the expression</returns>
        /// </summary>
        public LinearExpression CreateFreeCopy()
        {
            var expr_handle = LinearExpression_CreateFreeCopy(linear_expr_);
            CheckError();
            return LinearExpression.CreateFromHandle(expr_handle);
        }

        public void Dispose()
        {
            Cleanup();

            // Prevent the object from being placed on the
            // finalization queue
            System.GC.SuppressFinalize(this);
        }

        public static LinearExpression operator +(LinearExpression lin1, double a)
        {
            var tmp = lin1.CreateFreeCopy();
            tmp.AddConstant(a);
            return tmp;
        }
        public static LinearExpression operator +(double a, LinearExpression lin1) => lin1 + a;

        public static LinearExpression operator -(LinearExpression lin1, double a)
        {
            var tmp = lin1.CreateFreeCopy();
            tmp.AddConstant(-a);
            return tmp;
        }

        public static LinearExpression operator -(double a, LinearExpression lin1 )
        {
            var result = new LinearExpression(a);
            result.AddExpression(lin1, -1.0);
            return result;
        }

        public static LinearExpression operator +(LinearExpression lin1, LinearExpression lin2)
        {
            var tmp = lin1.CreateFreeCopy();
            tmp.AddExpression(lin2, 1.0);
            return tmp;
        }
        public static LinearExpression operator -(LinearExpression lin1, LinearExpression lin2)
        {
            var tmp = lin1.CreateFreeCopy();
            tmp.AddExpression(lin2, -1.0);
            return tmp;
        }

        public static Constraint operator <=(LinearExpression expr, double a)
        {
            return new Constraint(expr.CreateFreeCopy(), Constraint_sense.less_equal, a);
        }
        public static Constraint operator >=(LinearExpression expr, double a)
        {
            return new Constraint(expr.CreateFreeCopy(), Constraint_sense.greater_equal, a);
        }
        public static Constraint operator ==(LinearExpression expr, double a)
        {
            return new Constraint(expr.CreateFreeCopy(), Constraint_sense.equal, a);
        }

        public static Constraint operator !=(LinearExpression expr, double a)
        {
            throw new NotImplementedException();
        }

        public static Constraint operator <=(double a, LinearExpression expr) => expr >= a;
        public static Constraint operator >=(double a, LinearExpression expr) => expr <= a;
        public static Constraint operator ==(double a, LinearExpression expr) => expr == a;
        public static Constraint operator !=(double a, LinearExpression expr) => expr != a;
        public static LinearExpression operator *(LinearExpression expr, double a)
        {
            LinearExpression result = expr.CreateFreeCopy();
            for (int term_idx = 0; term_idx < result.GetTermsCount(); term_idx++)
            {
                var term_coef = result.GetTermCoeff(term_idx);
                result.SetTermCoeff(term_idx, term_coef * a);
            }
            result.SetConstant(result.GetConstant() * a);
            return result;
        }

        public static QuadExpression operator *(LinearExpression expr, Variable var)
        {
            QuadExpression result = new QuadExpression();
            for (int term_idx = 0; term_idx < expr.GetTermsCount(); term_idx++)
            {
                var term_coef = expr.GetTermCoeff(term_idx);
                var term_var = expr.GetTermVariable(term_idx);
                result.AddTerm(term_var, var, term_coef);
            }
            result.GetLinearPart().AddExpression( new LinearExpression( var, expr.GetConstant()), 1.0 );
            return result;
        }

        public static LinearExpression operator *(double a, LinearExpression expr) => expr * a;
        public static QuadExpression operator *(Variable var, LinearExpression expr) => expr * var;

        public static LinearExpression operator +(LinearExpression expr1, QuadExpression expr2)
        {
            LinearExpression result = expr1.CreateFreeCopy();
            QuadExpression quad_part = result.GetQuadPart();
            quad_part.AddExpression(expr2, 1.0);
            return result;
        }

        public static LinearExpression operator -(LinearExpression expr1, QuadExpression expr2)
        {
            LinearExpression result = expr1.CreateFreeCopy();
            QuadExpression quad_part = result.GetQuadPart();
            quad_part.AddExpression(expr2, -1.0);
            return result;
        }

        public static Constraint operator <=(LinearExpression left, QuadExpression right)
        {
            return new Constraint(left - right, Constraint_sense.less_equal, 0.0);
        }

        public static Constraint operator >=(LinearExpression left, QuadExpression right)
        {
            return new Constraint(left - right, Constraint_sense.greater_equal, 0.0);
        }

        public static Constraint operator ==(LinearExpression left, QuadExpression right)
        {
            return new Constraint(left - right, Constraint_sense.equal, 0.0);
        }

        public static Constraint operator !=(LinearExpression left, QuadExpression right)
        {
            throw new NotImplementedException();
        }
    }

    /// <summary>
    /// \~russian
    /// Обеспечивает работу с квадратичными выражениями - добавление/удаление элементов и изменение коэффициентов.
    /// Элемент выражения - переменная * переменная * коэффициент.
    /// 
    /// \~english
    /// Provides work with quadratic expressions - adding/removing elements and changing coefficients.
    /// Expression element - variable * variable * coefficient.
    /// 
    /// \~chinese-traditional
    /// Provides work with quadratic expressions - adding/removing elements and changing coefficients.
    /// Expression element - variable * variable * coefficient.
    /// </summary>
    public class QuadExpression : IDisposable
    {
        public override string ToString()
        {
            return ExpressionFormatter.FormatExpr(this);
        }

        public override bool Equals(object? obj)
        {
            return (this as object).Equals(obj);
        }
        public override int GetHashCode()
        {
            return (this as object).GetHashCode();
        }

        #region arhiplex_imports
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr QuadExpression_Create();

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern int QuadExpression_GetTermsCount(IntPtr quad_expr);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr QuadExpression_GetTermVariable1(IntPtr quad_expr, int term_idx);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern int QuadExpression_GetTermVariable1Index(IntPtr quad_expr, int term_idx);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr QuadExpression_GetTermVariable2(IntPtr quad_expr, int term_idx);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern int QuadExpression_GetTermVariable2Index(IntPtr quad_expr, int term_idx);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern double QuadExpression_GetTermCoeff(IntPtr quad_expr, int term_idx);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void QuadExpression_SetTermCoeff(IntPtr quad_expr, int term_idx, double value);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void QuadExpression_AddTerm(IntPtr quad_expr, IntPtr pVar1, IntPtr pVar2, double coeff);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void QuadExpression_AddExpression(IntPtr quad_expr, IntPtr pExpr, double mult);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void QuadExpression_RemoveTerm(IntPtr quad_expr, int term_idx);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr QuadExpression_GetLinearPart(IntPtr quad_expr);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr QuadExpression_CreateFreeCopy(IntPtr quad_expr);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern int QuadExpression_GetErrorCode(IntPtr quad_expr);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr QuadExpression_GetErrorText(IntPtr quad_expr);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl)]
        private static extern void QuadExpression_Release(IntPtr quad_expr);
        #endregion

        private void CheckError()
        {
            if (GetErrorCode() != 0)
            {
                throw new ArhiplexException(GetErrorCode(), GetErrorText() ?? "");
            }
        }
        private int GetErrorCode()
        {
            return QuadExpression_GetErrorCode(quad_expr_);
        }

        private string? GetErrorText()
        {
            IntPtr err_text = QuadExpression_GetErrorText(quad_expr_);
            return StringConverter.StringFromArhiplex(err_text);
        }

        private IntPtr quad_expr_;

        /// <summary>
        /// \~russian
        /// Создает пустое выражение
        /// 
        /// \~english
        /// Creates an empty expression
        /// 
        /// \~chinese-traditional
        /// Creates an empty expression
        /// </summary>
        public QuadExpression()
        {
            quad_expr_ = QuadExpression_Create();
        }

        /// <summary>
        /// \~russian
        /// Создает выражение на основе переменных и коэффициента
        /// <param name="var1">переменная №1 в выражении</param>
        /// <param name="var2">переменная №2 в выражении</param>
        /// <param name="coeff">коэффициент в выражении</param>     
        /// 
        /// \~english
        /// Creates an expression based on variables and coefficient
        /// <param name="var1">variable #1 in expression</param>
        /// <param name="var2">variable #2 in expression</param>
        /// <param name="coeff">coefficient in expression</param>     
        /// 
        /// \~chinese-traditional
        /// Creates an expression based on variables and coefficient
        /// <param name="var1">variable #1 in expression</param>
        /// <param name="var2">variable #2 in expression</param>
        /// <param name="coeff">coefficient in expression</param>     
        /// </summary>
        public QuadExpression(Variable var1, Variable var2, double coeff = 1.0)
        {
            quad_expr_ = QuadExpression_Create();
            AddTerm(var1, var2, coeff);
        }

        private QuadExpression(IntPtr quad_expr)
        {
            quad_expr_ = quad_expr;
        }
        internal static QuadExpression CreateFromHandle(IntPtr quad_expr) => new QuadExpression(quad_expr);
        public IntPtr Get()
        {
            return quad_expr_;
        }
        private void Cleanup()
        {
            if (quad_expr_ != IntPtr.Zero)
            {
                QuadExpression_Release(quad_expr_);
                quad_expr_ = IntPtr.Zero;
            }
        }

        public void Dispose()
        {
            Cleanup();

            // Prevent the object from being placed on the
            // finalization queue
            System.GC.SuppressFinalize(this);
        }

        ~QuadExpression()
        {
            Cleanup();
        }

        /// <summary>
        /// \~russian
        /// Возвращает кол-во элементов в выражении
        /// <returns>Кол-во элементов в выражении</returns>  
        /// 
        /// \~english
        /// Returns the number of elements in the expression
        /// <returns>Number of elements in the expression</returns>  
        /// 
        /// \~chinese-traditional
        /// Returns the number of elements in the expression
        /// <returns>Number of elements in the expression</returns>  
        /// </summary>
        public int GetTermsCount()
        {
            var res = QuadExpression_GetTermsCount(quad_expr_);
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Возвращает переменную №1 по индексу элемента в выражении
        /// <param name="term_idx">индекс элемента в выражении</param>
        /// <returns>объект переменной</returns>  
        /// 
        /// \~english
        /// Returns variable #1 by element index in expression
        /// <param name="term_idx">element index in expression</param>
        /// <returns>variable object</returns>  
        /// 
        /// \~chinese-traditional
        /// Returns variable #1 by element index in expression
        /// <param name="term_idx">element index in expression</param>
        /// <returns>variable object</returns>  
        /// </summary>
        public Variable GetTermVariable1(int term_idx)
        {
            var var_handle = QuadExpression_GetTermVariable1(quad_expr_, term_idx);
            CheckError();
            return new Variable(var_handle);
        }

        /// <summary>
        /// \~russian
        /// Возвращает переменную №2 по индексу элемента в выражении
        /// <param name="term_idx">индекс элемента в выражении</param>
        /// <returns>объект переменной</returns>  
        /// 
        /// \~english
        /// Returns variable #2 by element index in expression
        /// <param name="term_idx">element index in expression</param>
        /// <returns>variable object</returns>  
        /// 
        /// \~chinese-traditional
        /// Returns variable #2 by element index in expression
        /// <param name="term_idx">element index in expression</param>
        /// <returns>variable object</returns>  
        /// </summary>
        public Variable GetTermVariable2(int term_idx)
        {
            var var_handle = QuadExpression_GetTermVariable2(quad_expr_, term_idx);
            CheckError();
            return new Variable(var_handle);
        }

        /// <summary>
        /// \~russian
        /// Возвращает коэффициент переменных по индексу элемента в выражении
        /// <param name="term_idx">индекс элемента в выражении</param>
        /// <returns>Значение коэффициента</returns>  
        /// 
        /// \~english
        /// Returns the coefficient of variables by the index of the element in the expression
        /// <param name="term_idx">index of the element in the expression</param>
        /// <returns>Value of the coefficient</returns>  
        /// 
        /// \~chinese-traditional
        /// Returns the coefficient of variables by the index of the element in the expression
        /// <param name="term_idx">index of the element in the expression</param>
        /// <returns>Value of the coefficient</returns>  
        /// </summary>
        public double GetTermCoeff(int term_idx)
        {
            var res = QuadExpression_GetTermCoeff(quad_expr_, term_idx);
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Задаёт коэффициент переменной по индексу выражения
        /// <param name="term_idx">индекс элемента в выражении</param>
        /// <param name="value">Значение коэффициента при переменных в элементе</param>
        /// 
        /// \~english
        /// Sets the coefficient of the variable by the expression index
        /// <param name="term_idx">index of the element in the expression</param>
        /// <param name="value">Value of the coefficient for the variables in the element</param>
        /// 
        /// \~chinese-traditional
        /// Sets the coefficient of the variable by the expression index
        /// <param name="term_idx">index of the element in the expression</param>
        /// <param name="value">Value of the coefficient for the variables in the element</param>
        /// </summary>
        public void SetTermCoeff(int term_idx, double value)
        {
            QuadExpression_SetTermCoeff(quad_expr_, term_idx, value);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Добавляет элемент к выражению
        /// <param name="var1">переменная</param>
        /// <param name="var2">переменная</param>
        /// <param name="coeff">коэффициент</param>
        /// 
        /// \~english
        /// Adds an element to the expression
        /// <param name="var1">variable</param>
        /// <param name="var2">variable</param>
        /// <param name="coeff">coefficient</param>
        /// 
        /// \~chinese-traditional
        /// Adds an element to the expression
        /// <param name="var1">variable</param>
        /// <param name="var2">variable</param>
        /// <param name="coeff">coefficient</param>
        /// </summary>
        public void AddTerm(Variable var1, Variable var2, double coeff)
        {
            QuadExpression_AddTerm(quad_expr_, var1.Get(), var2.Get(), coeff);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Добавляет выражение к выражению
        /// <param name="quad_expr">квадратичное выражение</param>
        /// <param name="mult">коэффициент к добавляемому выражению</param>
        /// 
        /// \~english
        /// Adds an expression to an expression
        /// <param name="quad_expr">quadratic expression</param>
        /// <param name="mult">coefficient to the expression being added</param>
        /// 
        /// \~chinese-traditional
        /// Adds an expression to an expression
        /// <param name="quad_expr">quadratic expression</param>
        /// <param name="mult">coefficient to the expression being added</param>
        /// </summary>
        public void AddExpression(QuadExpression quad_expr, double mult = 1.0)
        {
            QuadExpression_AddExpression(quad_expr_, quad_expr.Get(), mult);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Удаляет элемент выражения по индексу
        /// <param name="term_idx">индекс удаляемого элемента в выражении</param>
        /// 
        /// \~english
        /// Removes an element of an expression by index
        /// <param name="term_idx">index of the element to remove in the expression</param>
        /// 
        /// \~chinese-traditional
        /// Removes an element of an expression by index
        /// <param name="term_idx">index of the element to remove in the expression</param>
        /// </summary>
        public void RemoveTerm(int term_idx)
        {
            QuadExpression_RemoveTerm(quad_expr_, term_idx);
            CheckError();
        }

        /// <summary>
        /// \~russian
        /// Возвращает линейную часть выражения
        /// <returns>линейная часть</returns>  
        /// 
        /// \~english
        /// Returns the linear part of the expression
        /// <returns>linear part</returns>
        /// 
        /// \~chinese-traditional
        /// Returns the linear part of the expression
        /// <returns>linear part</returns>
        /// </summary>
        public LinearExpression GetLinearPart()
        {
            var expr_handle = QuadExpression_GetLinearPart(quad_expr_);
            CheckError();
            return LinearExpression.CreateFromHandle(expr_handle);
        }

        /// <summary>
        /// \~russian
        /// Создает копию выражения, не привязанную к модели или другому ограничению
        /// <returns>копия выражения</returns>  
        /// 
        /// \~english
        /// Creates a copy of the expression that is not bound to a model or other constraint
        /// <returns>copy of the expression</returns>
        /// 
        /// \~chinese-traditional
        /// Creates a copy of the expression that is not bound to a model or other constraint
        /// <returns>copy of the expression</returns>
        /// </summary>
        public QuadExpression CreateFreeCopy()
        {
            var var_handle = QuadExpression_CreateFreeCopy(quad_expr_);
            CheckError();
            return new QuadExpression(var_handle);
        }

        public static QuadExpression operator +(QuadExpression expr1, Variable var)
        {
            QuadExpression result = expr1.CreateFreeCopy();
            LinearExpression linear = result.GetLinearPart();
            linear.AddExpression(new LinearExpression(var));
            return result;
        }

        public static QuadExpression operator -(QuadExpression expr1, Variable var)
        {
            QuadExpression result = expr1.CreateFreeCopy();
            LinearExpression linear = result.GetLinearPart();
            linear.AddExpression(new LinearExpression(var, -1.0));
            return result;
        }

        public static QuadExpression operator +(Variable var, QuadExpression expr1) => expr1 + var;

        public static QuadExpression operator -(Variable var, QuadExpression expr1)
        {
            LinearExpression lin_expr = new LinearExpression(var);
            QuadExpression result = lin_expr.GetQuadPart();
            result.AddExpression(expr1, -1.0);
            return result;
        }

        public static QuadExpression operator +(QuadExpression expr1, double a)
        {
            QuadExpression result = expr1.CreateFreeCopy();
            LinearExpression linear = result.GetLinearPart();
            linear.AddExpression(new LinearExpression(a));
            return result;
        }

        public static QuadExpression operator +(double a, QuadExpression expr1) => expr1 + a;

        public static QuadExpression operator -(QuadExpression expr1, double a)
        {
            QuadExpression result = expr1.CreateFreeCopy();
            LinearExpression linear = result.GetLinearPart();
            linear.AddExpression( new LinearExpression(-a));
            return result;
        }

        public static QuadExpression operator -(double a, QuadExpression expr1 )
        {
            LinearExpression linear = new LinearExpression(a);
            QuadExpression result = linear.GetQuadPart();

            result.AddExpression(expr1, -1.0);
            return result;
        }

        public static QuadExpression operator +(QuadExpression expr1, QuadExpression expr2)
        {
            QuadExpression result = expr1.CreateFreeCopy();
            result.AddExpression(expr2, 1.0);
            return result;
        }

        public static QuadExpression operator +(QuadExpression expr1, LinearExpression expr2)
        {
            QuadExpression result = expr1.CreateFreeCopy();
            LinearExpression linear_part = result.GetLinearPart();
            linear_part.AddExpression(expr2);
            return result;
        }

        public static QuadExpression operator -(QuadExpression expr1, LinearExpression expr2)
        {
            QuadExpression result = expr1.CreateFreeCopy();
            LinearExpression linear_part = result.GetLinearPart();
            linear_part.AddExpression(expr2, -1.0);
            return result;
        }

        public static QuadExpression operator -(QuadExpression expr1, QuadExpression expr2)
        {
            QuadExpression result = expr1.CreateFreeCopy();
            result.AddExpression(expr2, -1.0);
            return result;
        }

        public static Constraint operator <=(QuadExpression left, double a)
        {
            return new Constraint(left, Constraint_sense.less_equal, a);
        }

        public static Constraint operator <=(double a, QuadExpression right) => right >= a;
        public static Constraint operator >=(double a, QuadExpression right) => right <= a;
        public static Constraint operator ==(double a, QuadExpression right) => right == a;
        public static Constraint operator !=(double a, QuadExpression right) => right != a;
        public static Constraint operator >=(QuadExpression left, double a)
        {
            return new Constraint(left, Constraint_sense.greater_equal, a);
        }

        public static Constraint operator ==(QuadExpression left, double a)
        {
            return new Constraint(left, Constraint_sense.equal, a);
        }

        public static Constraint operator !=(QuadExpression left, double a)
        {
            throw new NotImplementedException();
        }

        public static Constraint operator <=(QuadExpression left, LinearExpression right)
        {
            return new Constraint(left-right, Constraint_sense.less_equal, 0.0);
        }

        public static Constraint operator >=(QuadExpression left, LinearExpression right)
        {
            return new Constraint(left - right, Constraint_sense.greater_equal, 0.0);
        }

        public static Constraint operator ==(QuadExpression left, LinearExpression right)
        {
            return new Constraint(left - right, Constraint_sense.equal, 0.0);
        }

        public static Constraint operator !=(QuadExpression left, LinearExpression right)
        {
            throw new NotImplementedException();
        }
    }

    static class ExpressionFormatter
    {
        public static string FormatExpr(QuadExpression quad_expr)
        {
            return ExprToString(quad_expr.GetLinearPart(), quad_expr);
        }

        public static string FormatExpr(LinearExpression linear_expr)
        {
            return ExprToString(linear_expr, linear_expr.GetQuadPart());
        }

        private static string ExprToString(LinearExpression linear_expr, QuadExpression quad_expr)
        {
            StringBuilder sb = new StringBuilder(1024);
            if (linear_expr.GetConstant() != 0.0)
                sb.Append($"{linear_expr.GetConstant()}");

            for( int i = 0; i < linear_expr.GetTermsCount(); ++i )
            {
                double coef = linear_expr.GetTermCoeff(i);
                if (coef == 0.0)
                    continue;
                if (sb.Length > 0 || coef < 0.0)
                {
                    sb.Append(coef > 0.0 ? " + " : " - ");
                }
                if(Math.Abs(coef) != 1.0)
                {
                    sb.Append( $"{Math.Abs(coef)} ");
                }

                var term_var = linear_expr.GetTermVariable(i);
                sb.Append(term_var.GetName());
            }

            bool bracket_printed = false;
            for (int i = 0; i < quad_expr.GetTermsCount(); ++i)
            {
                double coef = quad_expr.GetTermCoeff(i);
                if (coef == 0.0)
                    continue;
                if(!bracket_printed)
                {
                    sb.Append(" + [");
                }

                if (bracket_printed || coef < 0.0)
                {
                    sb.Append(coef > 0.0 ? " + " : " - ");
                }
                bracket_printed = true;
                if (Math.Abs(coef) != 1.0)
                {
                    sb.Append($"{Math.Abs(coef)} ");
                }

                var term_var1 = quad_expr.GetTermVariable1(i);
                var term_var2 = quad_expr.GetTermVariable2(i);
                sb.Append(term_var1.GetName());
                sb.Append(" ");
                sb.Append(term_var2.GetName());
            }
            if(bracket_printed)
            {
                sb.Append(" ]");
            }
            return sb.ToString();
        }
    }

    /// <summary>
    /// \~russian
    /// Предоставляет доступ к информации о расчете (количество итераций, статус, 
    /// значение целевой функции и пр.), а также к значениям переменных
    /// 
    /// \~english
    /// Provides access to calculation information (number of iterations, status, 
    /// objective function value, etc.), as well as to variable values
    /// 
    /// \~chinese-traditional
    /// Provides access to calculation information (number of iterations, status, 
    /// objective function value, etc.), as well as to variable values
    /// </summary>
    public class SolveResult : IDisposable
    {
        private void CheckError()
        {
            if (GetErrorCode() != 0)
            {
                throw new ArhiplexException(GetErrorCode(), GetErrorText() ?? "");
            }
        }

        private IntPtr solve_result_;

        #region arhiplex_imports
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern void SolveResult_Release(IntPtr solve_res);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern Solve_result SolveResult_GetSolveResult(IntPtr solve_res);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern Solution_status SolveResult_GetSolutionStatus(IntPtr solve_res);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern double SolveResult_GetRelativeGap(IntPtr solve_res);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]

        public static extern uint SolveResult_GetIterationsCount(IntPtr solve_res);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern Int64 SolveResult_GetProcessedNodesCount(IntPtr solve_res);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern double SolveResult_GetObjectiveFunctionValue(IntPtr solve_res);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern double SolveResult_GetBestBoundValue(IntPtr solve_res);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern double SolveResult_GetSolveTime(IntPtr solve_res); // секунды
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern double SolveResult_GetVariableValue(IntPtr solve_res, IntPtr var);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern double SolveResult_GetVariableValueByName(IntPtr solve_res, string var_name);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern double SolveResult_GetDualValue(IntPtr solve_res, IntPtr constr);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern double SolveResult_GetDualValueByName(IntPtr solve_res, string constr_name);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern double SolveResult_GetReducedCost(IntPtr solve_res, IntPtr var);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern double SolveResult_GetReducedCostByName(IntPtr solve_res, string var_name);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern double SolveResult_GetExpressionValueLinear(IntPtr solve_res, IntPtr expression);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern double SolveResult_GetExpressionValueQuad(IntPtr solve_res, IntPtr expression);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern void SolveResult_WriteSolution(IntPtr solve_res, string file_name);
        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern int SolveResult_GetErrorCode(IntPtr quad_expr);

        [DllImport("libarhiplex_cpp", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern IntPtr SolveResult_GetErrorText(IntPtr quad_expr);
        #endregion

        private SolveResult(IntPtr solve_result)
        {
            solve_result_ = solve_result;
        }
        
        internal static SolveResult CreateFromHandle(IntPtr obj_handle) => new SolveResult(obj_handle);

        public IntPtr Get()
        {
            return solve_result_;
        }
        private void Cleanup()
        {
            if (solve_result_ != IntPtr.Zero)
            {
                SolveResult_Release(solve_result_);
                solve_result_ = IntPtr.Zero;
            }
        }

        public void Dispose()
        {
            Cleanup();

            // Prevent the object from being placed on the
            // finalization queue
            System.GC.SuppressFinalize(this);
        }

        ~SolveResult()
        {
            Cleanup();
        }

        /// <summary>
        /// \~russian
        /// Получает статус процесса расчетов
        /// <returns>Значение статуса</returns>  
        /// 
        /// \~english
        /// Gets the status of the calculation process
        /// <returns>Status value</returns>
        /// 
        /// \~chinese-traditional
        /// Gets the status of the calculation process
        /// <returns>Status value</returns>
        /// </summary>
        public Solve_result GetSolveResult()
        {
            var res = SolveResult_GetSolveResult(solve_result_);
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Получает статус модели в итоге расчета
        /// <returns>Значение статуса</returns>  
        /// 
        /// \~english
        /// Gets the model status as a result of the calculation
        /// <returns>Status value</returns>
        /// 
        /// \~chinese-traditional
        /// Gets the model status as a result of the calculation
        /// <returns>Status value</returns>
        /// </summary>
        public Solution_status GetSolutionStatus()
        {
            var res = SolveResult_GetSolutionStatus(solve_result_);
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Получает точность решения в процентах
        /// <returns>точность решения в процентах</returns>  
        /// 
        /// \~english
        /// Gets the solution accuracy in percent
        /// <returns>solution accuracy in percent</returns>
        /// 
        /// \~chinese-traditional
        /// Gets the solution accuracy in percent
        /// <returns>solution accuracy in percent</returns>
        /// </summary>
        public double GetRelativeGap()
        {
            var res = SolveResult_GetRelativeGap(solve_result_);
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Получает количество итераций, проведенных в процессе расчета
        /// <returns>Кол-во итераций</returns>  
        /// 
        /// \~english
        /// Gets the number of iterations performed during the calculation
        /// <returns>Number of iterations</returns>
        /// 
        /// \~chinese-traditional
        /// Gets the number of iterations performed during the calculation
        /// <returns>Number of iterations</returns>
        /// </summary>
        public uint GetIterationsCount()
        {
            var res = SolveResult_GetIterationsCount(solve_result_);
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Получает количество обработанных узлов дерева решений
        /// <returns>Кол-во обработанных узлов</returns>  
        /// 
        /// \~english
        /// Gets the number of processed nodes of the decision tree
        /// <returns>Number of processed nodes</returns>
        /// 
        /// \~chinese-traditional
        /// Gets the number of processed nodes of the decision tree
        /// <returns>Number of processed nodes</returns>
        /// </summary>
        public Int64 GetProcessedNodesCount()
        {
            var res = SolveResult_GetProcessedNodesCount(solve_result_);
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Получает значение целевой функции
        /// <returns>Значение целевой функции</returns>  
        /// 
        /// \~english
        /// Gets the value of the objective function
        /// <returns>The value of the objective function</returns>
        /// 
        /// \~chinese-traditional
        /// Gets the value of the objective function
        /// <returns>The value of the objective function</returns>
        /// </summary>
        public double GetObjectiveFunctionValue()
        {
            var res = SolveResult_GetObjectiveFunctionValue(solve_result_);
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Получает значение граничной (двойственной) функции
        /// <returns>Значение граничной (двойственной) функции</returns>  
        /// 
        /// \~english
        /// Gets the value of the boundary (dual) function
        /// <returns>The value of the boundary (dual) function</returns>
        /// 
        /// \~chinese-traditional
        /// Gets the value of the boundary (dual) function
        /// <returns>The value of the boundary (dual) function</returns>
        /// </summary>
        public double GetBestBoundValue()
        {
            var res = SolveResult_GetBestBoundValue(solve_result_);
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Получает общее время решения
        /// <returns>Общее время решения, секунды</returns>  
        /// 
        /// \~english
        /// Gets the total solution time
        /// <returns>Total solution time, seconds</returns>
        /// 
        /// \~chinese-traditional
        /// Gets the total solution time
        /// <returns>Total solution time, seconds</returns>
        /// </summary>
        public double GetSolveTime()
        {
            var res = SolveResult_GetSolveTime(solve_result_);
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Получает значение переменной в решении
        /// <param name="var">объект переменной</param>
        /// <returns>Значение переменной в решении</returns>  
        /// 
        /// \~english
        /// Gets the value of a variable in the solution
        /// <param name="var">variable object</param>
        /// <returns>The value of the variable in the solution</returns>  
        /// 
        /// \~chinese-traditional
        /// Gets the value of a variable in the solution
        /// <param name="var">variable object</param>
        /// <returns>The value of the variable in the solution</returns>  
        /// </summary>
        public double GetVariableValue(Variable var)
        {
            var res = SolveResult_GetVariableValue(solve_result_, var.Get());
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Получает значение переменной в решении по имени 
        /// <param name="var_name">имя переменной</param>
        /// <returns>Значение переменной в решении</returns>  
        /// 
        /// \~english
        /// Gets the value of a variable in the solution by name
        /// <param name="var_name">variable name</param>
        /// <returns>Value of the variable in the solution</returns>  
        /// 
        /// \~chinese-traditional
        /// Gets the value of a variable in the solution by name
        /// <param name="var_name">variable name</param>
        /// <returns>Value of the variable in the solution</returns>  
        /// </summary>
        public double GetVariableValue(string var_name)
        {
            var res = SolveResult_GetVariableValueByName(solve_result_, var_name);
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Получает значение dual value
        /// <param name="constr">ограничение</param>
        /// <returns>Значение dual value</returns>  
        /// 
        /// \~english
        /// Gets the value of dual value
        /// <param name="constr">constraint</param>
        /// <returns>Value of dual value</returns>  
        /// 
        /// \~chinese-traditional
        /// Gets the value of dual value
        /// <param name="constr">constraint</param>
        /// <returns>Value of dual value</returns>  
        /// </summary>
        public double GetDualValue(Constraint constr)
        {
            var res = SolveResult_GetDualValue(solve_result_, constr.Get());
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Получает значение dual value по имени ограничения
        /// <param name="constr_name">имя ограничения</param>
        /// <returns>Значение dual value</returns>  
        /// 
        /// \~english
        /// Gets the value of dual value by constraint name
        /// <param name="constr_name">constraint name</param>
        /// <returns>Value of dual value</returns>  
        /// 
        /// \~chinese-traditional
        /// Gets the value of dual value by constraint name
        /// <param name="constr_name">constraint name</param>
        /// <returns>Value of dual value</returns>  
        /// </summary>
        public double GetDualValue(string constr_name)
        {
            var res = SolveResult_GetDualValueByName(solve_result_, constr_name);
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Получает значение reduced cost
        /// <param name="var">переменная</param>
        /// <returns>Значение reduced cost</returns>  
        /// 
        /// \~english
        /// Gets the reduced cost value
        /// <param name="var">variable</param>
        /// <returns>Reduced cost value</returns>  
        /// 
        /// \~chinese-traditional
        /// Gets the reduced cost value
        /// <param name="var">variable</param>
        /// <returns>Reduced cost value</returns>  
        /// </summary>
        public double GetReducedCost(Variable var)
        {
            var res = SolveResult_GetReducedCost(solve_result_, var.Get());
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Получает значение reduced cost
        /// <param name="var">имя переменной</param>
        /// <returns>Значение reduced cost</returns>  
        /// 
        /// \~english
        /// Gets the reduced cost value
        /// <param name="var_name">variable name</param>
        /// <returns>Reduced cost value</returns>  
        /// 
        /// \~chinese-traditional
        /// Gets the reduced cost value
        /// <param name="var_name">variable name</param>
        /// <returns>Reduced cost value</returns>  
        /// </summary>
        public double GetReducedCost(string var_name)
        {
            var res = SolveResult_GetReducedCostByName(solve_result_, var_name);
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Получает значение выражения
        /// <param name="expression">линейное выражение</param>
        /// <returns>Значение выражения</returns>  
        /// 
        /// \~english
        /// Gets the value of the expression
        /// <param name="expression">linear expression</param>
        /// <returns>Value of the expression</returns>  
        /// 
        /// \~chinese-traditional
        /// Gets the value of the expression
        /// <param name="expression">linear expression</param>
        /// <returns>Value of the expression</returns>  
        /// </summary>
        public double GetExpressionValue(LinearExpression expression)
        {
            var res = SolveResult_GetExpressionValueLinear(solve_result_, expression.Get());
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Получает значение выражения
        /// <param name="expression">квадратичное выражение</param>
        /// <returns>Значение выражения</returns>  
        /// 
        /// \~english
        /// Gets the value of the expression
        /// <param name="expression">quadratic expression</param>
        /// <returns>Value of the expression</returns>  
        /// 
        /// \~chinese-traditional
        /// Gets the value of the expression
        /// <param name="expression">quadratic expression</param>
        /// <returns>Value of the expression</returns>  
        /// </summary>
        public double GetExpressionValue(QuadExpression expression)
        {
            var res = SolveResult_GetExpressionValueQuad(solve_result_, expression.Get());
            CheckError();
            return res;
        }

        /// <summary>
        /// \~russian
        /// Записывает решение в файл
        /// <param name="file_name">путь к файлу решения для записи</param>
        /// 
        /// \~english
        /// Writes the solution to a file
        /// <param name="file_name">path to the solution file to write</param>
        /// 
        /// \~chinese-traditional
        /// Writes the solution to a file
        /// <param name="file_name">path to the solution file to write</param>
        /// </summary>
        public void WriteSolution(string file_name)
        {
            SolveResult_WriteSolution(solve_result_, file_name);
            CheckError();
        }

        private int GetErrorCode()
        {
            return SolveResult_GetErrorCode(solve_result_);
        }

        private string? GetErrorText()
        {
            IntPtr err_text = SolveResult_GetErrorText(solve_result_);
            return StringConverter.StringFromArhiplex(err_text);
        }
    }
}
