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); }
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); }
/// <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); }