/// <summary>
        /// Рассчитать расписание.
        /// </summary>
        public void StartSchedule()
        {
            // устанавливаем в каждое задание директивный срок
            SetDirectiveFromOrder(data.Orders, data.Tasks);
            // Сбрасываем предыдущее расписание
            data.Saws.ClearCompleteTasks();
            data.Grinders.ClearCompleteTasks();
            // устанавливаем текущее время
            double T = 0;
            // формируем список исходных заданий для распиливания
            TaskList DefaultSawingList = new TaskList();
            for (int i = 0; i < data.Tasks.Count; i++)
            {
                if (data.Tasks[i].SawingTime(data.Materials, data.Productions) != 0)
                {
                    DefaultSawingList.AddCopy((TaskItem)data.Tasks[i].Clone());
                }
            }

            // формируем список исходных заданий для шлифования из заданий не требующих распиливания
            TaskList DefaultPolishingList = new TaskList();
            for (int i = 0; i < data.Tasks.Count; i++)
            {
                if (data.Tasks[i].SawingTime(data.Materials, data.Productions) == 0 &&
                    data.Tasks[i].PolishingTime(data.Materials, data.Productions) != 0)
                {
                    DefaultPolishingList.AddCopy((TaskItem)data.Tasks[i].Clone());
                }
            }

            //
            // первая настройка пил
            //

            // сортируем пилы по возрастанию кол-ва поддерживаемых материалов
            for (int i = 0, i_end = data.Saws.Count - 1; i < i_end; i++)
            {
                for (int j = i + 1, j_end = data.Saws.Count; j < j_end; j++)
                {
                    if (data.Saws[i].SupportedMaterials.Count > data.Saws[j].SupportedMaterials.Count)
                    {
                        TimeDevice tmp = data.Saws[i];
                        data.Saws[i] = data.Saws[j];
                        data.Saws[j] = tmp;
                    }
                }
            }

            bool BigTaskFirst = data.Saws.Count < data.Grinders.Count;

            do
            {
                data.Saws.Service(T);
                // назначаем каждому устройству задание
                for (int i = 0, i_end = data.Saws.Count; i < i_end; i++)
                {
                    if (!data.Saws[i].IsBusy()) // если устройство не занято
                    {
                        int TaskId = data.Saws[i].MostImpTask(DefaultSawingList, data.Materials, data.Productions, BaseDeviceType.Saw, BigTaskFirst);

                        if (TaskId != -1)
                        {
                            int TaskIndex = DefaultSawingList.GetIndexById(TaskId);
                            int ProdIndex = data.Productions.GetIndexById(DefaultSawingList[TaskIndex].ProductionId);
                            data.Saws[i].AddTask(TaskId, DefaultSawingList[TaskIndex].MaterialId, T, DefaultSawingList[TaskIndex].SawingTime(data.Materials, data.Productions));
                            DefaultSawingList.DeleteById(TaskId);
                        }
                    }
                }

                data.Grinders.Service(T);
                // назначаем каждому станку
                for (int i = 0, i_end = data.Grinders.Count; i < i_end; i++)
                {
                    if (!data.Grinders[i].IsBusy()) // если устройство не занято
                    {
                        // получаем идентификатор задания с учётом того как настроено устройство
                        int FirstTaskId = data.Grinders[i].MostImpTask(DefaultPolishingList, data.Materials, data.Productions, BaseDeviceType.Grinder, BigTaskFirst);

                        // альтернативное задание
                        int SecondTaskId = data.Grinders[i].ImpTask(DefaultPolishingList, data.Materials, data.Productions, BaseDeviceType.Grinder);

                        if (FirstTaskId != SecondTaskId && SecondTaskId != -1)
                        {
                            int FirstTaskIndex = DefaultPolishingList.GetIndexById(FirstTaskId);
                            int FirstMaterialId = DefaultPolishingList[FirstTaskIndex].MaterialId;

                            // есть ли устройство - альтернатива текущему?
                            int AlternateDeviceId = data.Grinders.GetBestAlternative(FirstMaterialId, data.Grinders[i].Id, BaseDeviceType.Grinder);
                            if (AlternateDeviceId != -1) // альтернатива существует
                            {
                                int AlternateDeviceIndex = data.Grinders.GetIndexById(AlternateDeviceId);

                                int SecondTaskIndex = DefaultPolishingList.GetIndexById(SecondTaskId);
                                int SecondMaterialId = DefaultPolishingList[SecondTaskIndex].MaterialId;

                                int FirstProductIndex = data.Productions.GetIndexById(DefaultPolishingList[SecondTaskIndex].ProductionId);

                                // время обработки первого задания + конфигурация первого станка под второй материал
                                double FirstTime = DefaultPolishingList[FirstTaskIndex].PolishingTime(data.Materials, data.Productions) + data.Grinders[i].GetConfigTime(SecondMaterialId);

                                // время до конца отработки альтернативного устройства + время обработки первого задания + конфигурация второго станка под второй материал
                                double SecondTime = data.Grinders[AlternateDeviceIndex].Time + DefaultPolishingList.TaskDurationByMaterial(FirstMaterialId, data.Materials, data.Productions, BaseDeviceType.Grinder);
                                if (data.Grinders[AlternateDeviceIndex].CurrentMaterialId != FirstMaterialId)
                                    SecondTime += data.Grinders[AlternateDeviceIndex].GetConfigTime(FirstMaterialId);

                                if (SecondTime <= FirstTime)
                                {
                                    FirstTaskId = SecondTaskId;
                                }
                            }
                        }

                        if (FirstTaskId != -1)
                        {
                            int TaskIndex = DefaultPolishingList.GetIndexById(FirstTaskId);
                            int ProdIndex = data.Productions.GetIndexById(DefaultPolishingList[TaskIndex].ProductionId);
                            data.Grinders[i].AddTask(FirstTaskId, DefaultPolishingList[TaskIndex].MaterialId, T, DefaultPolishingList[TaskIndex].PolishingTime(data.Materials, data.Productions));
                            DefaultPolishingList.DeleteById(FirstTaskId);
                        }
                    }
                }

                //Определяем шаг по времени
                double Step = GetStep(data.Saws, data.Grinders);

                // совершаем временной шаг для пил
                for (int i = 0; i < data.Saws.Count; i++)
                {
                    int CompleteTaskId = data.Saws[i].MakeStep(Step);

                    // если завершилось распиливание
                    if (CompleteTaskId >= 0)
                    {
                        // получаем задание, которое прошло распиливание
                        int TaskIndex = data.Tasks.GetIndexById(CompleteTaskId);

                        // если завершенное задание требует шлифования, отправляем его в очередь на шлифовку
                        if (data.Tasks[TaskIndex].PolishingTime(data.Materials, data.Productions) != 0)
                        {
                            TaskItem TaskCopy = (TaskItem)data.Tasks[TaskIndex].Clone();
                            TaskCopy.TimeOfLastUsing = T;
                            DefaultPolishingList.AddCopy(TaskCopy);
                        }
                    }
                }

                // совершаем временной шаг шлифовщиков
                for (int i = 0; i < data.Grinders.Count; i++)
                {
                    int CompleteTaskId = data.Grinders[i].MakeStep(Step);
                }

                // изменяем текущее время
                T += Step;
            } while (data.Saws.OneWorkDeviceExist() || data.Grinders.OneWorkDeviceExist() ||
                    DefaultSawingList.Count != 0 || DefaultPolishingList.Count != 0);
        }
        /// <summary>
        /// Рассчитать расписание.
        /// </summary>
        public void StartSchedule2()
        {
            // устанавливаем в каждое задание директивный срок
            SetDirectiveFromOrder(data.Orders, data.Tasks);
            // Сбрасываем предыдущее расписание
            data.Saws.ClearCompleteTasks();
            data.Grinders.ClearCompleteTasks();
            // устанавливаем текущее время
            double T = 0;
            // формируем список исходных заданий для распиливания
            TaskList DefaultSawingList = new TaskList();
            for (int i = 0; i < data.Tasks.Count; i++)
            {
                if (data.Tasks[i].SawingTime(data.Materials, data.Productions) != 0)
                {
                    DefaultSawingList.AddCopy((TaskItem)data.Tasks[i].Clone());
                }
            }

            // формируем список исходных заданий для шлифования из заданий не требующих распиливания
            TaskList DefaultPolishingList = new TaskList();
            for (int i = 0; i < data.Tasks.Count; i++)
            {
                if (data.Tasks[i].SawingTime(data.Materials, data.Productions) == 0 &&
                    data.Tasks[i].PolishingTime(data.Materials, data.Productions) != 0)
                {
                    DefaultPolishingList.AddCopy((TaskItem)data.Tasks[i].Clone());
                }
            }

            //
            // первая настройка пил
            //

            // сортируем пилы по возрастанию кол-ва поддерживаемых материалов
            for (int i = 0, i_end = data.Saws.Count - 1; i < i_end; i++)
            {
                for (int j = i + 1, j_end = data.Saws.Count; j < j_end; j++)
                {
                    if (data.Saws[i].SupportedMaterials.Count > data.Saws[j].SupportedMaterials.Count)
                    {
                        TimeDevice tmp = data.Saws[i];
                        data.Saws[i] = data.Saws[j];
                        data.Saws[j] = tmp;
                    }
                }
            }

            do
            {
                data.Saws.Service(T);
                // назначаем каждому устройству задание
                for (int i = 0, i_end = data.Saws.Count; i < i_end; i++)
                {
                    if (!data.Saws[i].IsBusy()) // если устройство не занято
                    {
                        int TaskId = data.Saws[i].MostImpTask2(DefaultSawingList, data.Materials, data.Productions, BaseDeviceType.Saw);

                        if (TaskId != -1)
                        {
                            // получаем время до истечения директивногог срока

                            int TaskIndex = DefaultSawingList.GetIndexById(TaskId);

                            if (DefaultSawingList[TaskIndex].UseDeadLine)
                            {
                                // время через которое наступит deadline для задания
                                double TimeToExpare = (DefaultSawingList[TaskIndex].DeadLine - data.BaseTime.AddMinutes(T)).TotalMinutes;

                                if (TimeToExpare > 0) // если время ещё есть
                                {
                                    // рассмотрим, уложиться ли задание в директивный срок
                                    // в худшем случае относительно настройки и в лучшем случае относительно
                                    // свободности станков
                                    double RealWorkTime = DefaultSawingList[TaskIndex].SawingTime(data.Materials, data.Productions) +
                                                          DefaultSawingList[TaskIndex].PolishingTime(data.Materials, data.Productions) +
                                                          data.Saws.GetMidConfigTime(DefaultSawingList[TaskIndex].MaterialId) +
                                                          data.Grinders.GetMidConfigTime(DefaultSawingList[TaskIndex].MaterialId);

                                    // теперь определяем есть ли время, до которого необходимо вкрай начать выполнять это задание
                                    double DeltaTime = TimeToExpare - RealWorkTime;

                                    /*
                                    // определим примерное суммарное время заданий, которое нужно выполнить до этого же срока или ранее
                                    // примерное, потому что не учитываем настройку
                                    double TaskDurationSum = DefaultSawingList.GetTaskDurationByDeadLine(DefaultSawingList[TaskIndex].DeadLine, data.Materials, data.Productions);

                                    // получим количество таких заданий.
                                    // и представим что хотя бы для 20% заданий потребуется настройка
                                    int TaskCountByDeadLine = DefaultSawingList.GetTaskCountByDeadLine(DefaultSawingList[TaskIndex].DeadLine);

                                    // учтем время примерной настройки
                                    TaskDurationSum += 0.2 * TaskCountByDeadLine * (data.Saws.GetMidConfigTime(data.Materials) + data.Grinders.GetMidConfigTime(data.Materials));

                                    // получим новую дельту
                                    DeltaTime -= TaskDurationSum;
                                    */
                                    if (DeltaTime > 0)
                                    {
                                        // а теперь попробуем найти задание
                                        int AlterTaskId = data.Saws[i].MostImpTaskWithTimeLimit(DefaultSawingList, data.Materials, data.Productions, BaseDeviceType.Saw, data.Grinders, DeltaTime);
                                        if (AlterTaskId != -1) // если задание нашлось, то выполняем его
                                        {
                                            TaskId = AlterTaskId;
                                            TaskIndex = DefaultSawingList.GetIndexById(TaskId);
                                        }
                                    }
                                }
                            }

                            int ProdIndex = data.Productions.GetIndexById(DefaultSawingList[TaskIndex].ProductionId);

                            data.Saws[i].AddTask(TaskId, DefaultSawingList[TaskIndex].MaterialId, T, DefaultSawingList[TaskIndex].SawingTime(data.Materials, data.Productions));

                            DefaultSawingList.DeleteById(TaskId);
                        }
                    }
                }

                data.Grinders.Service(T);
                // назначаем каждому станку
                for (int i = 0, i_end = data.Grinders.Count; i < i_end; i++)
                {
                    if (!data.Grinders[i].IsBusy()) // если устройство не занято
                    {
                        /*

                        // получаем самое застарелое задание в очереди
                        int TaskId = data.Grinders[i].GetOldestTask(DefaultPolishingList);
                        if (TaskId != -1)
                        {
                            int TaskIndex = DefaultPolishingList.GetIndexById(TaskId);
                            int ProdIndex = data.Productions.GetIndexById(DefaultPolishingList[TaskIndex].ProductionId);
                            data.Grinders[i].AddTask(TaskId, DefaultPolishingList[TaskIndex].MaterialId, T, DefaultPolishingList[TaskIndex].SawingTime(data.Materials, data.Productions));
                            DefaultPolishingList.DeleteById(TaskId);
                        }
                        */

                        int TaskId = data.Grinders[i].MostImpTask2(DefaultPolishingList, data.Materials, data.Productions, BaseDeviceType.Grinder);

                        if (TaskId != -1)
                        {
                            int TaskIndex = DefaultPolishingList.GetIndexById(TaskId);
                            int ProdIndex = data.Productions.GetIndexById(DefaultPolishingList[TaskIndex].ProductionId);
                            data.Grinders[i].AddTask(TaskId, DefaultPolishingList[TaskIndex].MaterialId, T, DefaultPolishingList[TaskIndex].SawingTime(data.Materials, data.Productions));
                            DefaultPolishingList.DeleteById(TaskId);
                        }

                    }
                }

                //Определяем шаг по времени
                double Step = GetStep(data.Saws, data.Grinders);

                // совершаем временной шаг для пил
                for (int i = 0; i < data.Saws.Count; i++)
                {
                    int CompleteTaskId = data.Saws[i].MakeStep(Step);

                    // если завершилось распиливание
                    if (CompleteTaskId >= 0)
                    {
                        // получаем задание, которое прошло распиливание
                        int TaskIndex = data.Tasks.GetIndexById(CompleteTaskId);

                        // если завершенное задание требует шлифования, отправляем его в очередь на шлифовку
                        if (data.Tasks[TaskIndex].PolishingTime(data.Materials, data.Productions) != 0)
                        {
                            TaskItem TaskCopy = (TaskItem)data.Tasks[TaskIndex].Clone();
                            TaskCopy.TimeOfLastUsing = T;
                            DefaultPolishingList.AddCopy(TaskCopy);
                        }
                    }
                }

                // совершаем временной шаг шлифовщиков
                for (int i = 0; i < data.Grinders.Count; i++)
                {
                    int CompleteTaskId = data.Grinders[i].MakeStep(Step);
                }

                // изменяем текущее время
                T += Step;
            } while (data.Saws.OneWorkDeviceExist() || data.Grinders.OneWorkDeviceExist() ||
                    DefaultSawingList.Count != 0 || DefaultPolishingList.Count != 0);
        }