Экономический анализ. Разработка имитационных моделей управления запасами в цепях поставок Москва 2011 2 Введение
Скачать 3.44 Mb.
|
2.2 Модель с несколькими розничными точками Описание задачи Данная задача является расширенной версией предыдущей задачи. Увеличим количество розничных точек до восьми. Допустим, розничные точки и дистрибьютор расположены в пространстве следующим образом (координаты дистрибьютора (50,50)): Расстояние между розничной точкой и дистрибьютором рассчитывается по формуле: 65 Где и координаты розничной точки, а и координаты дистрибьютора. Ни у одной розничной точки недопустим дефицит более 1,5%. При транспортировке заказа десять единиц расстояния преодолевается за один день. Таблица параметров розничных точек: Параметры розничных точек Порядковый номер розн. точки 1 2 3 4 5 6 7 8 Координата x 1 10 4 6 9 3 5 8 Координата y 1 5 3 6 10 8 9 6 Спрос, mean 80 90 30 57 73 91 120 91 Спрос, std 18 20 30 12 25 17 34 25 Расстояние до дистрибьютора 6 5 3 2 7 4 4 4 Расстояние до дистрибьютора (в днях пути) округлено в большую сторону, так как спрос генерируется каждый день в самом начале дня (уровень запаса обновляется один раз в день в начале дня). Спрос задается нормальным распределением. Ставки издержек такие же, как и в предыдущей задаче. Задачи: 1. смоделировать и отразить на презентации работу цепи поставок; 2. найти оптимальные значения параметров управления запасами. Модификация класса Main 66 Основное отличие от предыдущей версии – количество розничных точек. При оптимизационном эксперименте требуется найти оптимальные параметры управления запасами для каждой из восьми розничных точек. Переименуем параметры ROP и EOQ в ROP1 и EOQ1: Добавим параметры ROP2…ROP8 и EOQ2…EOQ8: Значения по умолчанию параметров EOQ1...8: 1000, ROP1…8: 500. Как мы могли убедиться в предыдущем примере, оптимизационный эксперимент работает только с параметрами. Для удобства работы с параметрами создадим массивы переменных, где будут храниться их значения: 67 retEOQ – массив величин EOQ (размер заказа) розничных точек. Тип массива int[]. (Квадратные скобки – обозначение массива)[1,p21] Начальное значение: new int[]{EOQ1,EOQ2,EOQ3,EOQ4,EOQ5,EOQ6,EOQ7,EOQ8} retROP – массив величин ROP (пороговый уровень запаса) розничных точек. Тип массива int[]. Начальное значение: new int[]{ROP1,ROP2,ROP3,ROP4,ROP5,ROP6,ROP7,ROP8} Массивы retEOQ и retROP инициализируются значениями параметров EOQ1..8 и ROP1..8. Все переменные, которые связаны с розничными точками, в модели будут реализованы в виде массивов: 68 Меняем тип и значения по умолчанию для данных переменных: retStock – массив уровня запасов розничных точек. Тип: int[] Массив инициализируется строкой new int[] {0,0,0,0,0,0,0,0} . Каждый элемент приравнивается нулю. Нумерация элементов начинается от нуля. Индекс первого элемента 0, восьмого 7. demandMean, demandStd – массивы параметров нормального распределения спроса в розничных точках. Оба массива типа int[]. Начальное значение demandMean: new int[]{80,90,30,57,73,91,120,91} Начальное значение demandStd: new int[]{18,20,30,12,25,17,34,25} retShortage – массив учета случаев дефицита для каждой из розничных точек. Тип массива int[]. Начальное значение: new int[]{0,0,0,0,0,0,0,0} 69 retailCoordx и retailCoordy – массивы координат x и y розничных точек (координаты взяты из постановки задачи) Начальное значение retailCoordx: new int[]{10,100,40,60,90,30,50,80} Начальное значение retailCoordy: new int[]{10,50,30,60,100,80,90,60} Изменим код Действия при запуске класса Main: Добавим код, генерирующий и размещающий на карте розничные точки и генерирующий транспортные средства: for (int i = 0; i < 8; i++) { Retailer a = add_retailer(); a.setXY(4*retailCoordx[i],4*retailCoordy[i]); a.retDist= sqrt(pow(retailCoordx[i]-distrCoordx,2)+ pow(retailCoordy[i]-distrCoordy,2) ); retStock[i] = retEOQ[i]+retROP[i]; Truck d = add_truck(); d.setXY(4*distrCoordx,4*distrCoordy); d.rectangle.setVisible(false); } 70 В модели восемь розничных точек, поэтому инициализацию нужно выполнить для каждой из них. Для этого используем циклическая конструкция For. Границы цикла обозначены фигурными скобками. Переменная i – счетчик цикла. Каждую итерацию она увеличивается на единицу, начальное значение 0, конечное значение 7. Более подробная информация об использовании циклических конструкций в Java в материале «Java for Anylogic Users»[1,p37]. retailCoordx[i] – выбирает элемент массива с индексом i. Переменные уровня запасов и параметров управления запасами розничных точек также представлены в виде массивов. Далее в модель добавляется восемь транспортных средств, объектов класса Truck. Анимация модели будет организована таким образом, что транспортные средства видны лишь в процессе движения от дистрибьютора к розничной точке. Поэтому при запуске модели координаты должны соответствовать координатам дистрибьютора, но не быть видны в окне презентации. Метод d.rectangle.setVisible(false); делает объекты невидимыми на презентации. Код Действия при запуске класса Main: Distributor b = add_distributor(); b.setXY(4*distrCoordx,4*distrCoordy); for (int i = 0; i < 8; i++) { Retailer a = add_retailer(); a.setXY(4*retailCoordx[i],4*retailCoordy[i]); a.retDist= sqrt(pow(retailCoordx[i]-distrCoordx,2)+ pow(retailCoordy[i]-distrCoordy,2) ); retStock[i] = retEOQ[i]+retROP[i]; Truck d = add_truck(); d.setXY(4*distrCoordx,4*distrCoordy); d.rectangle.setVisible(false); } 71 Изменение алгоритма управления запасами Модифицируем диаграмму действий actionChart. В новой версии модели все действия выполняются для каждой из восьми розничных точек. Добавляем цикл For: Меняем параметры цикла: Тип: итератор по коллекции, элемент: Retailer ret, коллекция: retailer . Данный цикл перебирает все элементы коллекции retailer, которые мы создали в классе Main (всего восемь элементов). При этом «счетчиком цикла» будет выступать переменная ret. Переменная ret служит ссылкой на объект, по аналогии с использованием переменных a, b в Действии при запуске класса Main. В классе Main они использовались для инициализации, здесь – для удобства обработки. Перебор осуществляется, пока не закончатся элементы коллекции. В нашем случае у цикла будет восемь итераций. Более подробно о коллекциях в “Java for AnyLogic Users” [1,p21] Остальной код диаграммы действий нужно переместить внутрь цикла: 72 Для этого вырезаем (Ctrl+X) элемент «Спрос превышает запас ритейлера?» выделяем диаграмму цикла и вставляем (Ctrl+V) его около выделенной цветом области цикла, когда подсвечивается зеленая точка в конструкции цикла: Результат: 73 Элемент окончания работы диаграммы действий должен быть в единственном экземпляре и за пределами цикла: Добавим элемент Локальная переменная. Данная переменная будет соответствовать индексу элемента коллекции розничных точек: Изменяем параметры локальной переменной: 74 Добавим элемент Код сразу после локальной переменной: Меняем свойства элемента Код. Комментарий: Обновление retHoldingcost и генерация спроса, код: retHoldingcost+=ret.retholdingrate*retStock[j]; ret.currentDemand = round( max (normal(demandStd[j],demandMean[j]),0)); Код остальных модулей диаграммы действий изменяем с учетом работы с коллекцией агентов типа «розничная точка» (Retailer): Модуль: Спрос превышает запас ритейлера? 75 Код: ret.currentDemand > retStock[j] Модуль: Обновление дефицита Код: retShortage[j]++; Модуль: Нет заказа в пути? Код: truck.get(j).isMoving() == false Модуль: Обработка заказа дистрибьютором Код: create_RetReplenishment(ret.retDist/10,ret.retDist/10,j ); truck.get(j).rectangle.setVisible(true); truck.get(j).moveTo(ret.getX(),ret.getY()); Модуль: обновление уровня запаса ритейлера Код: retStock[j]-= ret.currentDemand; Модуль: Заказ ритейлера меньше запаса распр. центра и нет заказа в пути? Код: retStock[j] < retROP[j] && truck.get(j).isMoving() == false Модификация событий В событии ordering оставляем только две строки: totalCost=retHoldingcost+retTrcost; actionChart(); 76 Добавим параметр index (index соответствует индексу розничной точки – переменной j) в динамическое событие RetReplenishment: retStock[index]+=retEOQ[index]; truck.get(index).jumpTo(distributor.get(0).getX(),distr ibutor.get(0).getY()); truck.get(index).rectangle.setVisible(false); retTrcost+=time*retailer.get(index).rettrrate; Добавление элементов презентации Поменяем параметры Временного графика: 77 Добавим линию на окно редактирования класса Main: Поменяем динамические свойства линии line: Количество: 8. Данный массив линий будет использоваться для маршрутов «дистрибьютор – розничная точка». Чтобы линии автоматически перерисовывались при изменении конфигурации сети, изменим параметры расположения линий в пространстве: index – индекс линии (от 0 до 7) 78 Начальная точка каждой из линий – это координата розничной точки. Для получения координат используются методы getX() и getY(). X: retailer.get(index).getX() Y: retailer.get(index).getY() Параметры dX и dY определяют смещение конечной точки прямой по отношению к начальной. В нашем случае это разность между координатами дистрибьютора и розничной точки: dX: distributor.get(0).getX() - retailer.get(index).getX() dY: distributor.get(0).getY()- retailer.get(index).getY() Отображение уровня запаса в текстовом поле Добавим на презентацию модели переменные уровня запаса элементов сети. Для этого создадим элемент Текст: Переименуем текст в textRet. Текст должен отображать текущий уровень запаса розничной точки и быть расположен рядом с розничной точкой на карте презентации. Для этого снова воспользуемся динамическими параметрами: 79 Количество текстовых полей соответствует количеству розничных точек в модели. X: retailer.get(index).getX()+5 Y: retailer.get(index).getY() Текст: retStock[index] Координаты X и Y подобраны так, что текст будет располагаться немного правее (на 5 единиц) пиктограммы презентации розничной точки. Текстовое поле показывает текущий уровень запаса розничной точки retStock[]. Модифицируем текст, отображающий количество случаев возникновения дефицита. На его основе будут создаваться восемь текстовых полей, показывающих количество случаев возникновения дефицита в каждой из розничных точек: 80 Количество: 8 X: 500 Y: 10+15*index Текст: "Не выполненные в срок заказы,"+" Ритейлер № "+(1+index)+ ": "+retShortage[index] Координата Y задана таким образом, что строки будут располагаться вертикальным списком с отступом 15 единиц. Строка в поле текст состоит из нескольких элементов. Текст в кавычках будет отображаться как статическое текстовое поле, знак «+» объединяет части строки[1,p20]. Номер розничной точки на единицу больше индекса розничной точки, так как индекс начинает нумерацию с нуля. retShortage[index] выводит значение переменной, обозначающей уровень дефицита у данной розничной точки. Запустим модель: 81 На презентации отображаются запасы розничных точек, каждому маршруту соответствует линия, отображаются данные уровня издержек, количество случаев дефицита. Запись данных о ежедневном уровне запасов в текстовый файл Данные будут записываться в файл каждый день, поэтому отредактируем элемент «Обновление retHoldingcost и генерация спроса» диаграммы действий actionChart – добавим код для записи данных об уровне запасов в файл. В файле должно быть восемь столбцов (по количеству розничных точек) и тысяча строк (по количеству дней работы модели). Для записи используем методы print() – запись в строку и println() – начало новой строки. 82 filestock.print(" "+retStock[j]); if(j==7){ filestock.println(" "); } В текстовый файл добавляются пробел “ “ и величина текущего заказа розничной точки ret.retStock. Знак «+» используется для объединения двух частей выражения. Если выбран последний элемент коллекции с индексом 7, то начинается запись новой строки (в java нумерация элементов начинается с нуля). Запустим модель и откроем текстовый файл, в который были записаны данные во время работы симуляции: 83 Для того чтобы можно было открыть файл в Excel с нужным форматированием выбираем в Excel следующие параметры: Получаем следующую форму представления данных: 84 По аналогии с уровнем запасов розничной точки можно записывать в текстовые файлы и другие важные параметры работы модели для последующего более детального анализа полученных данных. Оптимизационный эксперимент Ограничения Модифицируем оптимизационный эксперимент. По условию ни в одной из розничных точек недопустимо количество дефицита более 15 случаев. Для того чтобы контролировать данное условие, добавим дополнительные переменные и код. Переменная shortageLevel соответствует допустимому уровню дефицита. Значение по умолчанию: 15. Переменная restriction равна нулю, если допустимый уровень дефицита не превышен, и единице в обратном случае. Обе переменные типа int и размещены в классе Main. Добавим код проверки превышения допустимого уровня дефицита в диаграмму действий: 85 Модуль: Обновление retHoldingcost и генерация спроса. Добавляем код: if (retShortage[j]>shortageLevel){ restriction = 1; } Вернемся к редактированию оптимизационного эксперимента: 86 В новой версии 16 изменяемых параметров модели. Настройки ограничений: Заново создаем интерфейс: Переместим элементы презентации оптимизационного эксперимента: 87 Поиск решения 88 Как мы видим, после определенного момента целевая функция (выделена синим цветом на графике) практически не улучшает значение. Задания: 1)Рассчитайте аналитически при помощи метода «центра тяжести» оптимальные координаты размещения дистрибьютора. Измените координаты distrCoordx и distrCoordy на полученные значения и проведите оптимизационный эксперимент. Сравните издержки новой конфигурации системы. 89 2)Доработайте модель таким образом, чтобы собирались данные по издержкам каждой розничной точки на хранение и транспортировку. 2.3 Расширенная модель цепи поставок Описание задачи Добавим в цепь поставок еще один уровень – поставщика. Ставка содержания запаса дистрибьютора: 1 единица за хранение единицы товара в течение периода, ставка на транспортировку: 20000 единиц за 10 единиц пройденного расстояния. Система управления запасами дистрибьютора реализована по аналогии с розничными точками: оптимальный размер заказа EOQ и пороговый уровень запаса ROP. Координаты поставщика: (60;120) 90 В модели восемь розничных точек, один дистрибьютор и один поставщик. Получаем восемнадцать изменяемых переменных, отвечающих за параметры управления запасами (запасы поставщика приняты неограниченными). Две дополнительные переменные определяют координаты расположения дистрибьютора. Таким образом, в модели двадцать изменяемых переменных. Дополнительные сложности связаны с вероятностной структурой спроса и ограничениями по допустимому уровню сервиса.[7] Задачи: 1.смоделировать и отразить на презентации работу расширенной цепи поставок; 2.найти оптимальные значения параметров управления запасами и оптимальные координаты размещения дистрибьютора. Добавление класса поставщика Создадим переменные координат поставщика в классе Main: Тип обеих переменных: int, значение по умолчанию supplyCoordx: 60, supplyCoordy: 120. По аналогии с агентом Distributor (во второй части практикума) добавим новый класс агентов Supplier. Цвет заливки для прямоугольника презентации Supplier – red, ширина и высота 7. Не забываем указать среду и выбрать реплицированный тип агента. Добавим параметры класса Distributor: 91 Параметры класса Distributor:расстояние до поставщика, проверка нахождения заказа в пути, ставка хранения запаса, ставка транспортировки от поставщика. dcDist – расстояние от дистрибьютора до поставщика. Тип параметра: double. dcholdingrate – ставка содержания одной единицы запаса на складе дистрибьютора за 1 день. Начальное значение: 1. Тип параметра: double. dctrrate – ставка на доставку заказа от поставщика до дистрибьютора за 1 день пути. Начальное значение: 20000. Тип: double Модификация класса Main Добавим параметры и переменные в класс Main: Переменные: DCHoldingcost – издержки на хранение запаса дистрибьютора. Тип переменной: Int DCTrcost – издержки на транспортировку дистрибьютора. Тип: double DCStock – текущий уровень запаса дистрибьютора. Тип: int DCShortage – переменная учета случаев дефицита дистрибьютора. Тип переменной int. Параметры: 92 DCEOQ – размер заказа дистрибьютора. Тип параметра int. Значение по умолчанию 15000: DCROP – величина порогового запаса дистрибьютора, значение по умолчанию 5000. Откорректируем Действие при запуске класса Main: Добавим строку инициализации запаса дистрибьютора: DCStock=DCEOQ+DCROP; Рассчитаем срок доставки заказа от поставщика дистрибьютору: b.dcDist = sqrt(pow(supplyCoordx-distrCoordx,2)+ pow(supplyCoordy-distrCoordy,2) ); Инициализируем объект класса Supplier: Supplier c = add_supplier(); c.setXY(4*supplyCoordx,4*supplyCoordy); 93 Данные две строки добавляют поставщика и размещают его на карте в соответствии с указанными координатами Также добавим еще одно транспортное средство, которое будет работать на маршруте «поставщик – дистрибьютор»: Truck e = add_truck(); e.setXY(4*supplyCoordx,4*supplyCoordy); truck.get(8).rectangle.setVisible(false); Мы уже создали восемь объектов truck (с индексами 0…7), поэтому индекс нового объекта будет равен восьми. Построим линию, соединяющую поставщика и дистрибьютора на карте: |