Ejemplo n.º 1
0
        public DailyPlanOutputModel Post([FromBody] DailyPlanInputModel input)
        {
            if (input == null)
            {
                return(null);
            }

            var result = input.Settings.OptimizationMethod == (int)OptimizationMethod.MIP ?  MIPPlanner.Solve(input) : CPPlanner.Solve(input);

            return(result);
        }
Ejemplo n.º 2
0
        public static DailyPlanOutputModel Solve(DailyPlanInputModel input)
        {
            var result = new DailyPlanOutputModel()
            {
                Rooms = new List <RoomOutputModel>(), HasSolution = true
            };

            foreach (var item in input.Rooms)
            {
                result.Rooms.Add(new RoomOutputModel {
                    Id = item.Id, Name = item.Name, Operations = new List <OperationOutputModel>()
                });
            }

            var solver = new Solver("SurgicaLogic", Solver.CBC_MIXED_INTEGER_PROGRAMMING);

            var operationsCount = input.Operations.Count;
            var roomsCount      = input.Rooms.Count;
            var totalPeriod     = input.Settings.MaximumPeriod;
            var overtime        = input.Settings.RoomsPeriod;

            var operations = Enumerable.Range(0, operationsCount);
            var rooms      = Enumerable.Range(0, roomsCount);
            var periods    = Enumerable.Range(0, totalPeriod);
            var overtimes  = Enumerable.Range(overtime - 1, totalPeriod - overtime);

            var operationNames = input.Operations.Select(x => x.Name).ToArray();
            var operationTimes = input.Operations.Select(x => x.Period).ToArray();
            var roomNames      = input.Rooms.Select(x => x.Name).ToArray();

            //3 boyutlu diziyi tanımladık.
            Variable[,,] production = new Variable[operationsCount, roomsCount, totalPeriod];
            for (int i = 0; i < operationsCount; i++)
            {
                for (int r = 0; r < roomsCount; r++)
                {
                    for (int t = 0; t < totalPeriod; t++)
                    {
                        production[i, r, t] = solver.MakeIntVar(0, 1, string.Format("{0}-{1}-{2}", i, r, t));
                    }
                }
            }

            //Overlap olmaması için
            for (int i1 = 0; i1 < operationsCount; i1++)
            {
                for (int i2 = 0; i2 < operationsCount; i2++)
                {
                    if (i2 != i1)
                    {
                        for (int r = 0; r < roomsCount; r++)
                        {
                            var longer = operationTimes[i1] > operationTimes[i2] ? operationTimes[i1] : operationTimes[i2];

                            for (int t = 0; t <= totalPeriod - longer; t++)
                            {
                                var timeList = Enumerable.Range(t, longer);

                                var c1 = (from t1 in timeList
                                          select production[i1, r, t1])
                                         .ToArray().Sum();

                                var c2 = (from t2 in timeList
                                          select production[i2, r, t2])
                                         .ToArray().Sum();

                                solver.Add(c1 + c2 <= 1);
                            }
                        }
                    }
                }
            }

            //Aynı doktora birden fazla ameliyat programlanmaması için
            for (int i1 = 0; i1 < operationsCount; i1++)
            {
                for (int i2 = 0; i2 < operationsCount; i2++)
                {
                    if (i2 != i1 && input.Operations[i1].DoctorIds.Intersect(input.Operations[i2].DoctorIds).Count() > 0)
                    {
                        var longer = operationTimes[i1] > operationTimes[i2] ? operationTimes[i1] : operationTimes[i2];

                        for (int t = 0; t <= totalPeriod - longer; t++)
                        {
                            var timeList = Enumerable.Range(t, longer);

                            var c1 = (from t1 in timeList
                                      from r in rooms
                                      select production[i1, r, t1])
                                     .ToArray().Sum();

                            var c2 = (from t2 in timeList
                                      from r in rooms
                                      select production[i2, r, t2])
                                     .ToArray().Sum();

                            solver.Add(c1 + c2 <= 1);
                        }
                    }
                }
            }

            //Uygun olmayan odalarda ameliyat yapılamasın.
            for (int i = 0; i < operationsCount; i++)
            {
                for (int r = 0; r < roomsCount; r++)
                {
                    if (input.Operations[i].UnavailableRooms.Any(x => x == input.Rooms[r].Id))
                    {
                        solver.Add((from t in periods
                                    select production[i, r, t])
                                   .ToArray().Sum() == 0);
                    }
                }
            }

            //Her ameliyat bir kez yapılsın.
            for (int i = 0; i < operationsCount; i++)
            {
                solver.Add((from r in rooms
                            from t in periods
                            select production[i, r, t])
                           .ToArray().Sum() == 1);
            }

            //aynı odada aynı anda 2 ameliyat olmasın
            for (int r = 0; r < roomsCount; r++)
            {
                for (int t = 0; t < totalPeriod; t++)
                {
                    solver.Add((from i in operations
                                select production[i, r, t])
                               .ToArray().Sum() <= 1);
                }
            }

            //Ameliyat overtime dahil süreden daha uzun sürmesin diye bu kontrolü yapıyorum.
            for (int i = 0; i < operationsCount; i++)
            {
                int operationTime = operationTimes[i];
                if (operationTime > 1)
                {
                    var tList = Enumerable.Range(totalPeriod - operationTime + 1, operationTime - 1);

                    solver.Add((from r in rooms
                                from t in tList
                                select production[i, r, t])
                               .ToArray().Sum() == 0);
                }
            }

            //Overtime'ı minimize ediyoruz.
            solver.Minimize((from i in operations
                             from r in rooms
                             from t in periods
                             select production[i, r, t] * t)
                            .ToArray().Sum());

            ////Overtime'ı minimize ediyoruz.
            //solver.Minimize((from i in operations
            //                 from r in rooms
            //                 from t in overtimes
            //                 select production[i, r, t])
            //                 .ToArray().Sum());

            if (solver.Solve() != Solver.OPTIMAL)
            {
                result.HasSolution = false;
                return(result);
            }

            for (int i = 0; i < operationsCount; i++)
            {
                for (int r = 0; r < roomsCount; r++)
                {
                    for (int t = 0; t < totalPeriod; t++)
                    {
                        if (production[i, r, t].SolutionValue() == 1)
                        {
                            var surgeryRoom = result.Rooms[r];
                            var dateTime    = new DateTime(input.Settings.OperationDate.Year, input.Settings.OperationDate.Month, input.Settings.OperationDate.Day, input.Settings.StartingHour, input.Settings.StartingMinute, 0);
                            dateTime = dateTime.AddMinutes(t * input.Settings.PeriodInMinutes);
                            surgeryRoom.Operations.Add(new OperationOutputModel {
                                Id = input.Operations[i].Id, Name = input.Operations[i].Name, DoctorIds = input.Operations[i].DoctorIds, Period = input.Operations[i].Period, StartDate = dateTime, OperationTime = input.Operations[i].OperationTime
                            });
                        }
                    }
                }
            }

            return(result);
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Çözümlemeyi yapan ana metot.
        /// </summary>
        /// <param name="Operations">Planlanacak ameliyatlar.</param>
        /// <param name="Rooms">Ameliyathaneler</param>
        /// <param name="Settings.MaximumPeriod">Overtime dahil ameliyatlara ayrılacak maksimum süre.</param>
        /// <param name="Settings.RoomsPeriod">Ameliyathanelerin uygunluk süresini belirtir. Maksimum değer, mesai süresi olmalı.</param>
        /// <param name="Settings.StartingHour">Ameliyatların saat kaçta başlayacağı bilgisi</param>
        /// <param name="Settings.StartingMinute">Ameliyatların hangi dakikada başlayacağı bilgisi. Buçukta da başlayabilirse diye.</param>
        /// <param name="Settings.PeriodInMinutes">Ameliyat periyodlarının kaç dakika olduğu bilgisi</param>
        public static DailyPlanOutputModel Solve(DailyPlanInputModel input)
        {
            var result = new DailyPlanOutputModel()
            {
                Rooms = new List <RoomOutputModel>()
            };

            foreach (var item in input.Rooms)
            {
                result.Rooms.Add(new RoomOutputModel {
                    Id = item.Id, Name = item.Name, Operations = new List <OperationOutputModel>()
                });
            }

            Solver solver = new Solver("SurgicaLogic");

            var roomPeriodIndex = Enumerable.Range(0, input.Rooms.Count * input.Settings.MaximumPeriod).ToList();

            //Buraya dummy bir status daha ekliyorum. Bunu, ameliyat süresinden daha ilerideki ihtimallere atayacağım.
            var dummyIndex = input.Rooms.Count * input.Settings.MaximumPeriod + 100;

            roomPeriodIndex.Add(dummyIndex);
            //Geçerli statüler. Maksimum süre dahil tüm statüleri içerir. Örnegin 2 ameliyathane olan bir senaryoda 1= 1.ameliyathanenin 1. periyodunu, 2= 2. ameliyathanenin 1.periyodunu, 3= 1.ameliyathanenin 2. periyodunu, 4= 2.ameliyathanenin 2. periyodunu vs. temsil eder.
            var validStatus = roomPeriodIndex.ToArray();

            //AllDifferent methodunda kullanmak üzere maksimum süreyi ienumerable olarak tutan değişkenler.
            IEnumerable <int> maximumLengthList = Enumerable.Range(0, input.Settings.MaximumPeriod);

            //Ameliyatların hangi oda-zaman diliminde yapılacağına karar verilen değişken. Tüm ihtimaller bu değişken içerisine tanımlanıyor.
            IntVar[,] x =
                solver.MakeIntVarMatrix(input.Operations.Count, input.Settings.MaximumPeriod, validStatus, "x");

            //Yukarıdaki değişkeni çözümleyebilmek için tek boyutlu hali.
            IntVar[] x_flat = x.Flatten();

            //Hangi zaman dilimlerine atama yaptığımı anlayabilmek için ameliyat - süre ilişkisini iki boyutlu dizi olarak tutuyorum. İki ameliyatın aynı oda-zaman dilimine atanmasını bu şekilde engelliyorum.
            int[,] preview = new int[input.Operations.Count, input.Settings.MaximumPeriod];

            if (input.Operations.Any(t => t.Period > input.Settings.MaximumPeriod))
            {
                return(result);
            }

            //Ameliyatların aynı odada devam edebilmesi ve önceki bir zamana atama yapmaması için, bir sonraki değerin bir önceki değerden en az oda sayısı kadar büyük olması olması kuralı.
            for (int i = input.Operations.Count - 1; i >= 0; i--)
            {
                for (int j = input.Operations[i].Period - 1; j >= 0; j--)
                {
                    solver.Add(x[i, j] != dummyIndex);

                    if (j > 0)
                    {
                        for (int k = 1; k < input.Rooms.Count; k++)
                        {
                            solver.Add(x[i, j] > x[i, j - 1] + k);
                        }
                    }
                }
            }

            for (int i = 0; i < input.Operations.Count; i++)
            {
                int[] doctorIds = input.Operations[i].DoctorIds;

                //Bir ameliyat bir odada veya bir zamanda yapılamaz bilgisi bu değişkende tutuluyor.
                int[] blockedTimes = GetBlockedTimes(input.Operations[i].UnavailableRooms, input.Rooms, input.Settings.MaximumPeriod, validStatus);

                foreach (var item in blockedTimes)
                {
                    for (int m = 0; m < input.Settings.MaximumPeriod; m++)
                    {
                        //Burada bu ameliyat bu zamanda, bu odada yapılamaz kuralını ekliyoruz.
                        if (item != dummyIndex)
                        {
                            //Console.WriteLine("x[{0}, {1}] != {2}", i, m, item);
                            solver.Add(x[i, m] != item);
                        }
                    }
                }

                //Aynı doktora aynı anda birden fazla ameliyat planlanmaması için
                for (int ad = 0; ad < input.Operations.Count; ad++)
                {
                    //Eğer sonraki ameliyatı yapacak doktorlardan birisi, bu ameliyatı yapan doktor ise.
                    if (input.Operations[ad].DoctorIds.Intersect(doctorIds).Count() > 0 && ad != i)
                    {
                        //Bir sonraki ameliyat süresi kadar dön.
                        for (int ml = 0; ml < input.Operations[ad].Period; ml++)
                        {
                            //Mevcut ameliyatın süresi kadar dön.
                            for (int t = 0; t < input.Operations[i].Period; t++)
                            {
                                var div  = solver.MakeDiv(x[i, t], input.Rooms.Count);
                                var prod = solver.MakeProd(div, input.Rooms.Count);

                                //Ameliyat odası sayısı kadar dön.
                                for (int r = 0; r < input.Rooms.Count; r++)
                                {
                                    solver.Add(x[ad, ml] != solver.MakeSum(prod, r));
                                }
                            }
                        }
                    }
                }

                bool ameliyatBasladi = false;
                bool forCompleted    = false;
                for (int j = 0; j < input.Settings.MaximumPeriod; j++)
                {
                    //Bu for daha önce tamamlanmadıysa. En dışta ameliyatlar içerisinde döndüğüm için bir ameliyat için iki kez planlama yapılmasın diye bu kontrolü yapıyorum.
                    if (!forCompleted)
                    {
                        for (int k = 1; k < input.Operations[i].Period; k++)
                        {
                            //Eğer ameliyat için yeterli süre varsa, atama yapacağım yere daha önce herhangi bir ameliyat planlamadıysam.
                            if (j < input.Settings.MaximumPeriod - input.Operations[i].Period && preview[i, j + k] == 0)
                            {
                                //Ameliyatların aynı odada yapılmasını sağlamak için yeni atayacağımız değeri, bir önceki  değere ameliyathane sayısı kadar ekleyerek buluyoruz. Yani a1'de başladıysa, 2 ameliyathane varsa bir sonraki değer a3 olmalı.
                                //Console.WriteLine("x[{0}, {1} + {2}] == x[{0}, {1} + {2} - 1] + {3}", i, j, k, operationRoomCount);
                                solver.Add(x[i, j + k] == x[i, j + k - 1] + input.Rooms.Count);

                                preview[i, j + k]     = 1;
                                preview[i, j + k - 1] = 1;
                                ameliyatBasladi       = true;
                            }
                        }
                    }

                    //Eğer ameliyat başladıysa yukarıdaki for tamamlanmış demektir.
                    if (ameliyatBasladi == true)
                    {
                        forCompleted = true;
                    }
                }
            }

            for (int i = 1; i < input.Operations.Count; i++)
            {
                for (int j = 0; j < i; j++)
                {
                    //Bundan önce yapılan tüm ameliyatların odaZaman değerleri, o an yapılan ameliyatın odaZaman değerlerinden farklı olsun.
                    var list = (from k in maximumLengthList.Take(input.Operations[j].Period)
                                select x[j, k]).ToList();
                    for (int t = 0; t < input.Operations[i].Period; t++)
                    {
                        list.Add(x[i, t]);
                        //Console.WriteLine("x[{0},{1}]", i, t);
                    }

                    solver.Add(list.ToArray().AllDifferent());
                }
            }

            //Burada da atama yapılmayan yerlere varsayilan bir deger atiyorum ki farkli ihtimallerde farkli senaryolar görünebilsin. Yoksa atama yapilmayan yerdeki ihtimalleri degistirip sonuçta ayni programi çikartiyordu.
            for (int i = 1; i < input.Operations.Count; i++)
            {
                for (int j = 0; j <= i; j++)
                {
                    //Bundan önce yapilan tüm ameliyatlarin bitisinden maksimum süreye kadar olan degerleri, hep ayni olsun diyorum ki kendi üretmesini istedigim ihtimaller farklilassin.
                    var list = (from k in maximumLengthList.Skip(input.Operations[j].Period)
                                select x[j, k]).ToList();

                    foreach (var item in list)
                    {
                        solver.Add(item == dummyIndex);
                    }
                }
            }

            // İhtimalleri bu kriterlere göre oluştur.
            DecisionBuilder db = solver.MakePhase(x_flat,
                                                  Solver.CHOOSE_MIN_SIZE_LOWEST_MIN,
                                                  Solver.ASSIGN_MIN_VALUE);

            solver.NewSearch(db);

            int num_solutions = 0;

            while (solver.NextSolution())
            {
                Console.WriteLine();
                num_solutions++;
                List <int> roomUsage      = new List <int>();
                var        operationTimes = new List <int>();
                for (int i = 0; i < input.Operations.Count; i++)
                {
                    //Console.Write("Ameliyat #{0,-2}: ", input.Operations[i].Id);
                    for (int j = 0; j < input.Operations[i].Period; j++)
                    {
                        int v = (int)x[i, j].Value();
                        if (j == 0)
                        {
                            int valueIndex  = Array.IndexOf(validStatus, v);
                            int room        = v % input.Rooms.Count;
                            var surgeryRoom = result.Rooms[room];
                            int time        = room == input.Rooms.Count - 1 ? (valueIndex + 1) / input.Rooms.Count : ((valueIndex + 1) / input.Rooms.Count) + 1;
                            var dateTime    = new DateTime(input.Settings.OperationDate.Year, input.Settings.OperationDate.Month, input.Settings.OperationDate.Day, input.Settings.StartingHour, input.Settings.StartingMinute, 0);
                            dateTime = dateTime.AddMinutes((time - 1) * input.Settings.PeriodInMinutes);
                            //Console.Write(surgeryRoom.Id + "-" + surgeryRoom.Name + " Ameliyathanesi, Başlangıç: " + dateTime.ToShortTimeString() + ", Bitiş: " + dateTime.AddMinutes(input.Operations[i].Period * input.Settings.PeriodInMinutes).ToShortTimeString());
                            //Console.Write(surgeryRoom.Id + ". Oda, Saat: " + dateTime.ToShortTimeString());
                            operationTimes.Add(dateTime.Hour);

                            surgeryRoom.Operations.Add(new OperationOutputModel {
                                Id = input.Operations[i].Id, Name = input.Operations[i].Name, DoctorIds = input.Operations[i].DoctorIds, Period = input.Operations[i].Period, StartDate = dateTime, OperationTime = input.Operations[i].OperationTime
                            });
                        }
                        roomUsage.Add(v);
                    }
                }

                //Console.WriteLine("Usage statistics per room:\n");
                //for (int i = 0; i < input.Rooms.Count; i++)
                //{
                //    Console.Write("Oda #{0,-2}: ", i + 1);
                //    var usage = i + 1 == input.Rooms.Count ? roomUsage.Count(p => (p % input.Rooms.Count == 0)) : roomUsage.Count(p => (p % input.Rooms.Count == i + 1));
                //    var usageTimes = i + 1 == input.Rooms.Count ? roomUsage.Where(p => (p % input.Rooms.Count == 0)) : roomUsage.Where(p => (p % input.Rooms.Count == i + 1));
                //    int overTime = CalculateOverTime(usage, input.Settings.RoomsPeriod); //usageTimes.Count(t => t > operationRoomCount * roomPeriodLength[i]);
                //    Console.Write("Uygunluk: {0}, Kullanılan Süre: {1}, Overtime: {2}", input.Settings.RoomsPeriod, usage, overTime);
                //    Console.WriteLine();
                //}

                //We just show 1 solution
                if (num_solutions > 0)
                {
                    result.HasSolution = true;
                    break;
                }
            }

            solver.EndSearch();

            return(result);
        }
        public async Task <DailyPlanOutputModel> GenerateOperationPlan([FromBody] GenerateOperationPlanInputModel input)
        {
            var result = new DailyPlanOutputModel();

            var allOperations = await _operationStoreService.GetOperationsByDateAsync(input.OperationDate);

            var rooms = await _operatingRoomStoreService.GetAvailableRoomsAsync(input.OperationDate);

            var operations = new List <Planning.Model.InputModel.OperationInputModel>();

            var systemSettings = await _settingStoreService.GetAllAsync();

            var workingHourStart   = systemSettings.SingleOrDefault(x => x.Key == SettingKey.OperationWorkingHourStart.ToString());
            var workingHourEnd     = systemSettings.SingleOrDefault(x => x.Key == SettingKey.OperationWorkingHourEnd.ToString());
            var period             = systemSettings.SingleOrDefault(x => x.Key == SettingKey.OperationPeriodInMinutes.ToString());
            var optimizationMethod = systemSettings.SingleOrDefault(x => x.Key == SettingKey.OptimizationMethod.ToString());

            foreach (var operation in allOperations)
            {
                //Bu operasyonun yapılabileceği odaları, operasyonun tipi üzerinden giderek buluyorum.
                var operatingRoomIds = operation.OperationType.OperatingRoomOperationTypes.Where(x => x.IsActive).Select(x => x.OperatingRoomId);
                var personnelIds     = operation.OperationPersonels.Where(x => x.Personnel.PersonnelCategory.SuitableForMultipleOperation != true);

                var outputModel = AutoMapper.Mapper.Map <Model.OutputModel.OperationOutputModel>(operation);

                operations.Add(new Planning.Model.InputModel.OperationInputModel
                {
                    Id            = operation.Id,
                    Name          = operation.Name,
                    Period        = operation.OperationTime % period.IntValue.Value == 0 ? operation.OperationTime / period.IntValue.Value : operation.OperationTime / period.IntValue.Value + 1,
                    DoctorIds     = personnelIds.Select(x => x.PersonnelId).ToArray(),
                    OperationTime = operation.OperationTime,
                    //Bu operasyonun yapılamayacağı odaları, tüm odalardan yapılabileceği odaları çıkartarak buluyorum.
                    UnavailableRooms = rooms.Select(x => x.Id).Except(operatingRoomIds.Except(outputModel.BlockedOperatingRoomIds)).ToList()
                });
            }

            var surgeryPlan = new DailyPlanInputModel
            {
                Settings = new SettingsInputModel
                {
                    RoomsPeriod        = Convert.ToInt32(workingHourEnd.TimeValue.HourToDateTime().Subtract(workingHourStart.TimeValue.HourToDateTime()).TotalMinutes) / period.IntValue.Value,
                    MaximumPeriod      = Convert.ToInt32((new DateTime(input.OperationDate.AddDays(1).Year, input.OperationDate.AddDays(1).Month, input.OperationDate.AddDays(1).Day) - workingHourStart.TimeValue.HourToDateTime()).TotalMinutes) / period.IntValue.Value,
                    StartingHour       = workingHourStart.TimeValue.HourToDateTime().Hour,
                    StartingMinute     = workingHourStart.TimeValue.HourToDateTime().Minute,
                    PeriodInMinutes    = period.IntValue.Value,
                    OperationDate      = input.OperationDate,
                    OptimizationMethod = optimizationMethod.SettingValueId ?? (int)OptimizationMethod.MIP
                },
                Rooms      = rooms,
                Operations = operations
            };

            string req = JsonConvert.SerializeObject(surgeryPlan);

            using (var client = new HttpClient()
            {
                BaseAddress = new Uri(AppSettings.ApiBaseUrl), Timeout = TimeSpan.FromMinutes(10)
            })
            {
                HttpResponseMessage response = await client.PostAsync(AppSettings.ApiPostUrl, new StringContent(req, Encoding.Default, "application/json"));

                if (response.Content != null)
                {
                    await _operationPlanStoreService.DeletePlanByDateAsync(input.OperationDate);

                    var responseContent = await response.Content.ReadAsStringAsync();

                    var apiResultModel = JsonConvert.DeserializeObject <DailyPlanOutputModel>(responseContent);

                    foreach (var room in apiResultModel.Rooms)
                    {
                        foreach (var operation in room.Operations)
                        {
                            var model = new OperationPlanModel
                            {
                                OperatingRoomId   = room.Id,
                                OperationId       = operation.Id,
                                OperationDate     = operation.StartDate,
                                RealizedStartDate = operation.StartDate,
                                RealizedEndDate   = operation.StartDate.AddMinutes(operation.OperationTime)
                            };

                            await _operationPlanStoreService.InsertAsync(model);
                        }
                    }

                    await _operationPlanStoreService.SaveChangesAsync();

                    return(apiResultModel);
                }
            }

            return(result);
        }