+{Page}FleetFlow ! 1 Initializations /02/09/2003 07:12:46.758 !---------------------------------------- +{Object.1} FleetFlow.[1]Initializations INTEGER:: intZone,intZone1 +{Trans} CALL OpenTableFile(21,WithPath("tables.HTM")) ! Spreadsheet saved in html format CALL GetTable(21,{Table.9}DaysOfWeek) CALL GetTable(21,{Table.D}TruckLdDemand) CALL GetTable(21,{Table.D}ControlsAndParams) CLOSE(21) !----- !Put data from ControlsAndParams table into scalar variables for easy... !recognition in other parts of the model &B.FleetSize = {t.D}ControlsAndParams(1,1) &D.InvCostPerTruck = {t.D}ControlsAndParams(2,1) &D.SalvCostPerTruck = {t.D}ControlsAndParams(3,1) &D.FleetTruckCostPerDay = {t.D}ControlsAndParams(4,1) &D.OutSrcTruckCostPerDay = {t.D}ControlsAndParams(5,1) &I.PlanningHorizonYrs = {t.D}ControlsAndParams(6,1) &R.StopTriggerDay = {t.D}ControlsAndParams(7,1) !When to stop flow analysis !---------------------------------------- &T.MaxZones = tnyMaxZones !Integer constant defined in ADD file !---------------------------------------- tnyTripDays(:) = (/({t.D}TruckLdDemand(8,intZone),intZone=1,tnyMaxZones)/) WRITE(9,*)"Looped initialization (demo) of tnyTripDays",tnyTripDays !---------------------------------------- tnyTripDays(:) = {t.D}TruckLdDemand(8,:) !8th row vector from table WRITE(9,*)"Array section assignment of tnyTripDays:",tnyTripDays !---------------------------------------- !Get search stimulus from Optimization module... !Overrides fleet size read above from Excel table CALL OptimizationStimulus(&B.FleetSize) !---------------------------------------- WRITE(9,*)"&B.FleetSize",&B.FleetSize WRITE(9,*)"&D.InvCostPerTruck",&D.InvCostPerTruck WRITE(9,*)"&D.SalvCostPerTruck",&D.SalvCostPerTruck WRITE(9,*)"&D.FleetTruckCostPerDay",&D.FleetTruckCostPerDay WRITE(9,*)"&D.OutSrcTruckCostPerDay",&D.OutSrcTruckCostPerDay WRITE(9,*)"&T.MaxZone",&T.MaxZones WRITE(9,*)"&I.PlanningHorizonYrs",&I.PlanningHorizonYrs WRITE(9,*)"&R.StopTriggerDay",&R.StopTriggerDay !---------------------------------------- IF($L.FirstCall)OPEN(23,file=WithPath("ResultsP5.txt"),action="WRITE") !---------------------------------------- !Write top of Results file WRITE(23,'(A)')"Fleet Flow Analysis" WRITE(23,'(A)')"-------------------" WRITE(23,*) IF($L.Search)WRITE(23,*)$511.StimMsg(:$B.Stim)," Iteration",$B.Probe WRITE(23,*)"Fleet size:",&B.FleetSize,"trucks" WRITE(23,*) !Column headings (see Reuslts file to see how the header comes out) WRITE(23,'(A)')"Day WeekDay Demand Supply Idle OutSrce1 "// & "OutSrce2 OutSrce3 En route Fleet en route" !---------------------------------------- !All four vars below are vectors with &T.MaxZones elements regOutSource(:) = 0 !Outsourced truckloads to each zone on a day regFleetRuns(:) = 0 !Total fleet trucks sent to each zone regOutSrcRuns(:) = 0 !Total outsourced truckloads to each zone !----- dblFleetOpCost(:) = 0. dblOutSourceCost(:) = 0. &D.TotalOperCost = 0. !Total fleet operating cost &D.TotalOutSrcCost = 0. !Total outsourcing cost &R.Day = 0 &T.DayOfWeek = 0 &R.FleetEnRoute = 0 !Trucks in fleet that are on the road (not idle) !----- &R.Idle = &B.FleetSize !All trucks are idle at start of run @R.OutSource = 0 &R.OutSourceEnRoute = 0 &R.EnRoute = 0 !Fleet and outsourced trucks on the road &R.Supply = 0 &L.Collect = logNo !----- !Initialize objects for tracking demand (and other) min & max info mmxDemand = mmxInit mmxEnroute = mmxInit mmxOutSource = mmxInit !total en route mmxIdle = mmxInit !----- -{Trans} +{SignalTarget} !SignalTarget procedure property executes when a node is finished !There has to be one flow into n[5] through a[8] on first day. Following... !signal will create that flow into n[5]. !----- $D.SignalDuration = 1.0 {Signal.8} !---------------------------------------- !A normal flow will occur through a[4] into n[5], following the flow ... !from this node (n[1]) to n[2]DayTracking. The number of flows through ... !a[7] to n[5] every day should equal &T.MaxZones (which is 3 in this ... !case), signifying the return of trucks every day from all zones. ! !Since there is no flow at the start of the run, we need to create 3 ... !flows into n[5] along a[7] for the first day. ! !On the second day, there will be one arriving normally from previous ... !day after 1 day's run, so we will need to create only 2 artificial ... !flows for trucks arriving after 2-day and 3-day runs. ! !On the third day, there will be one returning from 2nd day after a ... !1-day trip and another returning from 1st day after a 2-day trip. So, ... !we will need only one artificial flow to account for trucks returning ... !from 3-day trips. ! !The following logic implements the explanation above. There is a ... !technically easier way to structure the nested loops below, but the ... !way it is implemented is easier to explain (as above). !----- @R.FleetTrucks = 0 !All signals in the loop below will inherit this value DO intZone = 1, &T.MaxZones !To arrive on day 1 through day &T.MaxZones @T.RouteZone = intZone !The upper limit on the loop below will cause &T.MaxZones=3 flows on... !day 1, &T.MaxZone-1=2 flows on day 2, &T.MaxZone-2=1 flows on day 3, etc. !The expression for upper limit will ensure the right number of ... !artificial flows, whatever the value of &T.MaxZones DO intZone1 = 1, &T.MaxZones - (intZone - 1) !Temporal arc duration will be zero. Time to reach n[5] will be ... !based on the expression for Duration on a[7]. {Signal.7} END DO END DO -{SignalTarget} -{Object.1} FleetFlow.[1]Initializations !---------------------------------------- +{Object.2} FleetFlow.[2]DayTracker +{Trans} &T.DayOfWeek = &T.DayOfWeek +1 IF(&T.DayOfWeek > 7)&T.DayOfWeek = 1 &R.Day = &R.Day + 1 !When a column vector is stored in Excel, column index ... !value of 1 needs to be used to retrieve any element of the vector &9.DayofWeek = strDaysOfWeek(&T.DayOfWeek,1) !----- !Logical global property variable LastDay will be set to true if... !current day is equal to the day for stopping current execution of ... !the model. == is the logical operator for testing for equality &L.LastDay = &R.Day == &R.StopTriggerDay !----- -{Trans} -{Object.2} FleetFlow.[2]DayTracker !---------------------------------------- +{Object.3} FleetFlow.[3]DayTracker--->|DayTracker +{Branch} {nop.L}Branch={.NOT. &L.LastDay} -{Branch} +{Trans} -{Trans} +{Duration} {nop.D}Duration={1}... -{Duration} -{Object.3} FleetFlow.[3]DayTracker--->|DayTracker !---------------------------------------- +{Object.4} FleetFlow.[4]DayTracker-->>|AssignTrucks +{Trans} {nop.P}Trans:{@R.FleetTrucks = 0} -{Trans} -{Object.4} FleetFlow.[4]DayTracker-->>|AssignTrucks !---------------------------------------- +{Object.5} FleetFlow.[5]AssignTrucks INTEGER:: intZone +{Merging} {nop.P}Merging:{&R.Supply = MergeSum(@R.FleetTrucks)} -{Merging} +{Trans} !Total supply is fleet trucks arriving from all zones and fleet trucks in ... !idle state following previous dispatch &R.Supply = &R.Supply + &R.Idle !----- WRITE(9,*)"&T.DayOfWeek",&T.DayOfWeek !Tracing day of week in feedback file !Total demand on a given day is the sum of truckloads that must be ... !dispatched that day to all zones. We could have calculated these... !and put them in a column in the TruckLdDemand table in the spreadsheet,... !but we decided to do it this way in spite of minor computational ... !inefficiency just to show Fortran 95's SUM operation on array section. &R.TotalDemand = SUM({t.D}TruckLdDemand(&T.DayOfWeek,:)) -{Trans} +{SignalTarget} !----- !SignalTarget procedure property executes when a node is finished !Since we want to assign fleet trucks to the longest routes possible ... !and assign outsourced trucks to the smallest routes possible, the ... !following algorithm is used to achieve that objective. This allocation ... !strategy may be myopic, but probably not terribly bad. Stil, it is only ... !a guess and a hope. &L.Collect = &R.Day > 7 .AND. &R.Day < 15 &R.Idle = &R.Supply !----- DO intZone = &T.MaxZones, 1, -1 !Going down from &T.MaxZones to 1 @T.RouteZone = intZone &R.ZoneNdemand = {t.D}TruckLdDemand(&T.DayOfWeek,intZone) IF(&R.Idle >= &R.ZoneNdemand)THEN @R.FleetTrucks = &R.ZoneNdemand @R.OutSource = 0 &R.Idle = &R.Idle - @R.FleetTrucks ELSE @R.FleetTrucks = &R.Idle @R.OutSource = &R.ZoneNdemand - @R.FleetTrucks &R.Idle = 0 ENDIF regFleetDispatch(intZone) = @R.FleetTrucks regOutSource(intZone) = @R.OutSource {Signal.7} END DO !----- &R.EnRoute = &R.EnRoute + &R.TotalDemand !Since all demands are always fulfilled @R.TotOutSource = SUM(regOutSource(:)) &R.OutSourceEnRoute = &R.OutSourceEnRoute + @R.TotOutSource &R.FleetEnRoute = &R.EnRoute - &R.OutSourceEnRoute !----- IF(Time() < &R.StopTriggerDay)THEN !Put a separator in the results file whenever a new week starts IF(&T.DayOfWeek == 1)WRITE(23,*) !----- !Write results of current day into Results file !!!!!!!Modify WRITE if number of zones changes from 3 to something else WRITE(23,'(I3,A10,I7,I8,I6,I10,I10,I10,I10,I16)')&R.Day,&9.DayofWeek, & &R.TotalDemand,&R.Supply,&R.Idle,regOutSource(1),regOutSource(2), & regOutSource(3),&R.EnRoute,&R.FleetEnRoute ENDIF !----- -{SignalTarget} -{Object.5} FleetFlow.[5]AssignTrucks !---------------------------------------- +{Object.6} FleetFlow.[6]Initializations--->|DayTracker +{Duration} {nop.D}Duration={1} -{Duration} -{Object.6} FleetFlow.[6]Initializations--->|DayTracker !---------------------------------------- +{Object.7} FleetFlow.[7]AssignTrucks-->>|AssignTrucks !Trucks are back at the origin at this point; update all statuses +{Branch} {nop.L}Branch={logNo !Traversed by signals from AssignTrucks node} -{Branch} +{Duration} {nop.D}Duration={{t.D}TruckLdDemand(8,@T.RouteZone)} -{Duration} +{Flow} {nop.P}Flow:{0;0;0;0;tnyMaxZones} -{Flow} +{SignalTarget} !SignalTarget procedure property executes when an arc is finished !In the case of this arc, it means trucks from a zone have arrived... !back at the base &R.EnRoute = &R.EnRoute - @R.FleetTrucks - @R.OutSource &R.FleetEnRoute = &R.FleetEnRoute - @R.FleetTrucks &R.OutSourceEnRoute = &R.OutSourceEnRoute - @R.OutSource -{SignalTarget} -{Object.7} FleetFlow.[7]AssignTrucks-->>|AssignTrucks !---------------------------------------- +{Object.8} FleetFlow.[8]AssignTrucks-->>|AssignTrucks +{Trans} {nop.P}Trans:{@R.FleetTrucks = 0}... !FleetTrucks property of entity is initialized to zero to ensure... !that trucks coming out of idle state are not added twice to ... !the supply figure when MergeSum function is used at n[5]AssignTrucks IF(&L.Collect)THEN !Track min and max idle trucks and the day when they occur CALL TrackMinMax(mmxIdle,&R.Idle,&9.DayofWeek) ENDIF -{Trans} -{Object.8} FleetFlow.[8]AssignTrucks-->>|AssignTrucks !---------------------------------------- +{Object.9} FleetFlow.[9]OutsourcingCalcs +{Trans} !ADD file contains AccumulateOpCost procedure CALL AccumulateOpCost(&D.TotalOutSrcCost,dblOutSourceCost(:), & &D.OutSrcTruckCostPerDay,tnyTripDays(:),regOutSource(:)) !Track total outsourced runs to each zone; vector addition operation regOutSrcRuns(:) = regOutSrcRuns(:) + regOutSource(:) !Track maximum and minimum outsourced and the days when they occur !mmx objects are defined and declared in ADD file CALL TrackMinMax(mmxOutSource,@R.TotOutSource,&9.DayofWeek) -{Trans} -{Object.9} FleetFlow.[9]OutsourcingCalcs !---------------------------------------- +{Object.11} FleetFlow.[11]FinalCalcs INTEGER:: intIndx +{Trans} WRITE(23,*) WRITE(23,'(A,T31,I14)')"Trucks in fleet",&B.FleetSize WRITE(23,'(A,T31,I14)')"Planning horizon (years)",&I.PlanningHorizonYrs WRITE(23,'(A,T31,I14)')"Operating weeks per year",52 WRITE(23,'(A,T31,I14/)')"Planning horizon (weeks)",&I.PlanningHorizonYrs * 52 !----- &D.NetInvCostPerTruck = &D.InvCostPerTruck - &D.SalvCostPerTruck !----- WRITE(23,'(A,T31,F15.0)')"Investment cost per truck",&D.InvCostPerTruck WRITE(23,'(A,T31,F15.0)')"Salvage per truck",&D.SalvCostPerTruck WRITE(23,'(A,T31,F15.0/)')"Net investment per truck",&D.NetInvCostPerTruck !----- WRITE(23,'(A,T31,F15.0)')"Weekly fleet operating costs",&D.TotalOperCost WRITE(23,'(A,T31,F15.0/)')"Weekly outsourcing costs",&D.TotalOutSrcCost !----- &D.NetInvCost = &D.NetInvCostPerTruck * &B.FleetSize &D.TotalOperCost = &D.TotalOperCost * &I.PlanningHorizonYrs * 52 !----- WRITE(23,'(A,T31,F15.0)')"Total fleet investment",&D.NetInvCostPerTruck * &B.FleetSize WRITE(23,'(A,T31,F15.0/)')"Fleet operating costs",&D.TotalOperCost !----- &D.TotalOutSrcCost = &D.TotalOutSrcCost * &I.PlanningHorizonYrs * 52 &D.TotalCost = &D.NetInvCost + &D.TotalOperCost + &D.TotalOutSrcCost !----- WRITE(23,'(A,T31,F15.0)')"Total fleet costs",&D.NetInvCost + &D.TotalOperCost WRITE(23,'(A,T31,F15.0)')"Outsourcing costs",&D.TotalOutSrcCost WRITE(23,'(A,T31,F15.0/)')"Total costs",&D.TotalCost !----- WRITE(23,'(A/)')"Weekly loads allocation:" WRITE(23,'(A)')"Route zone Fleet Outsourced" WRITE(23,'(I10,2I15)')(intIndx,regFleetRuns(intIndx), & regOutSrcRuns(intIndx),intIndx=1,&T.MaxZones) !----- WRITE(23,*) WRITE(23,'(A)')" Demand Idle OutSrce En route" WRITE(23,'(A3,I16,I12,I12,I12)')"Min",mmxDemand%regMinVal, & mmxIdle%regMinVal,mmxOutSource%regMinVal,mmxEnroute%regMinVal WRITE(23,'(A3,I16,I12,I12,I12/)')"Max",mmxDemand%regMaxVal, & mmxIdle%regMaxVal,mmxOutSource%regMaxVal,mmxEnroute%regMaxVal !----- WRITE(23,'(A7,A12,A12,A12,A12)')"Min day",TRIM(mmxDemand%strMinValDay), & TRIM(mmxIdle%strMinValDay),TRIM(mmxOutSource%strMinValDay), & TRIM(mmxEnroute%strMinValDay) WRITE(23,'(A7,A12,A12,A12,A12/)')"Max day",TRIM(mmxDemand%strMaxValDay), & TRIM(mmxIdle%strMaxValDay),TRIM(mmxOutSource%strMaxValDay), & TRIM(mmxEnroute%strMaxValDay) !----- !Give Optimization module this model's response to the stimulus... !OptimizationStimulus will automatically raise $L.YellowFlag, causing... !NMOD to stop current execution of the model and return control to the... !search procedure, even if the analyst has not built into the what-if... !model logic for stopping infinite execution of cycles. !Second argument in the CALL is optional. When a string is present, search... !procedure writes that string and the stimulus and response to the screen CALL OptimizationResponse(&B.FleetSize,&D.TotalCost,"Results: ") $L.YellowFlag = logYes -{Trans} -{Object.11} FleetFlow.[11]FinalCalcs !---------------------------------------- +{Object.12} FleetFlow.[12]AssignTrucks--->|OutsourcingCalcs +{Branch} {nop.L}Branch={&L.Collect} -{Branch} -{Object.12} FleetFlow.[12]AssignTrucks--->|OutsourcingCalcs !---------------------------------------- +{Object.13} FleetFlow.[13]DayTracker--->|FinalCalcs +{Branch} {nop.L}Branch={&L.LastDay}... -{Branch} +{Duration} {nop.D}Duration={1} -{Duration} -{Object.13} FleetFlow.[13]DayTracker--->|FinalCalcs !---------------------------------------- +{Object.14} FleetFlow.[14]FleetCalcs +{Trans} !ADD file contains the AccumulateOpCost procedure for adding cost of ... !operating trucks on each route zone run. !---------------------------------------- CALL AccumulateOpCost(&D.TotalOperCost,dblFleetOpCost(:), & &D.FleetTruckCostPerDay,tnyTripDays(:),regFleetDispatch(:)) !Track total weekly fleet runs to each zone regFleetRuns(:) = regFleetRuns(:) + regFleetDispatch(:) !----- !Track maximum and minimum demands and trucks en route, and the days... !when they occur. mmx objects are defined and declared in ADD file CALL TrackMinMax(mmxDemand,&R.TotalDemand,&9.DayofWeek) CALL TrackMinMax(mmxEnroute,&R.EnRoute,&9.DayofWeek) -{Trans} -{Object.14} FleetFlow.[14]FleetCalcs !---------------------------------------- +{Object.15} FleetFlow.[15]AssignTrucks--->|FleetCalcs +{Branch} {nop.L}Branch={&L.Collect} -{Branch} -{Object.15} FleetFlow.[15]AssignTrucks--->|FleetCalcs -{Page}FleetFlow //////////////////////////////////////////