# Collaborative demand forecasting

Do you have questions or comments about this model? Ask them here! (You'll first need to log in.)

## WHAT IS IT?

Supply chain resilience has gained more importance in recent years due to the trend for e-commerce. The industry has a tendency to sell products in low quantity, with high demand variation. To increase the resilience of the supply chain, demand forecasting is necessary. The model compares two approaches towards forecasting. One case focuses on forecasting only on individual distributor's history and another case considers the possibility to share information and forecast on all distributors' histories.

## HOW IT WORKS

In the beginining of every tick, household selects a distribution function and determines the ratio of order. The ratio informs the household to increase or lower their order. Than the ratio is multiplied by variation of product quantity (This approach is used to simulate different size of households). Every beginning of tick household sends an order to the distributor. The distributor fulfills the order the same tick and the stock level is updated. If the stock level is negative, the backlog is evaluated. Than distributors must order products from suppliers, however there is 1 tick delay for product delivery from supplier. Therefore, the demand is forecasted in advanced and supplier delivery are made in advanced. If there is a backlog, it is being added to the order to the supplier during the next tick, to increase the stock level.

## HOW TO USE IT

You can set the distributors quantity and household quantity. A limit is set of how much agents you can have to decrease the limit of hardware.

Share_information is used to change the forecasting approach. Forecast on individual company's history (off), forecast on group companies' history (on).

Market_type = oligopoly. Is used to simulate a small market where only few distributors exists, in this case the households always places orders. Market_type = perfect competition. The market type has multiple distributors, which are not involved in the simulation. Than the households sometimes does not buy any products assuming that they buy from other distributors, which information we do not have.

Safety_level is used to determine the necessary stock level, which must be held to have a possibility to deal with drastic demand increase. The main idea of the forecasting approach is to have as little stock as possible. The level is set to a percentege of previous 7 day demand history.

mean_treshold. Due to the random distributions and product quantity, sometimes the households all order the maximum amount at these times the demand drastically spikes, which disrupts the results of the model. Therefore, there is set a limit of how many times the demand and forecasting results can increase when comparing with the mean demand.

consumer_loyalty. If consumer loyalty is turned on, consumers selects only 1 distributor in the setup stage and always stays loyal to it. Of the consumer loyalty is turned off, every tick the consumers selects different distributor.

## THINGS TO NOTICE

Real companies' sales histories are fitted on distributions with R package "tdistrplus".

The forecasting is done with a machine learning approach, Extreme learning machine (ELM). The high variation in demand variation is also necessary to limit the ELM ability to adapt to the variation precisely. The ELM algorithm has quick neural hidden layer computation capabilities, however in temrs of precision is weak. This particular algorithm has been chosen due to processing power limitations.

When using the behaviour space it is important to set parallel running process to 1, because the r code is used to suite linear programming and not parallel.

## THINGS TO TRY

The main idea is to compare individual demand forecasting vs group demand forecasting in different context. Its recommended to leave mean sliders as default and change only: Distributor*N, Clients*N, share*information, safety*level, market*Type and consumer*loyalty.

Distributor*N and Clients*N can help to simulate different market size (e.g. Belgium vs USA).

Share_information helps to compare individual and group demand forecasting in the same context.

Safty*level is used to help limit the stock level. When the safety*level changes its important to evaluate the backlog level.

Market_type compares market type with many sellers (perfect competition) vs few sellers (oligopoly).

consumer_loyalty. This parameter simulates loyalty discounts and benefits vs random consumer behavior depending on lower price, availability or other criteria.

## EXTENDING THE MODEL

The model can be extended by introducing more product type variation, evaluating the costs of warehousing. The machine learning approach could also be used not only to make predictions, but also choose the safety_level and other variables on its own.

## NETLOGO FEATURES

I have used extension R to implement machine learning. NetLogo does not have built in futures.

## RELATED MODELS

http://modelingcommons.org/browse/one*model/3378#model*tabs*browse*info

## CREDITS AND REFERENCES

Extension R: https://ccl.northwestern.edu/netlogo/docs/r.html

Prediction method is not accurate, but has a very fast hidden layer evaluation, which is suitable for simulations with less computation power: http://www.ntu.edu.sg/home/egbhuang/

## Comments and Questions

;; forecasting is done in R environment ;; Before using the model it is important to install NetLogo 5.3.1, R code above 3.2, extension R (if it is not present) and the newest rJava package in R ;; Configuration on windows environment variables: ;; create new variable - name: R_HOME, path: C:\Program Files\R\R-3.3.3 ;; create new variable - name: JRI_HOME, path: C:\Users\user\Documents\R\win-library\3.3\rJava\jri ;; Add to existing variable - name: Path, path: C:\Program Files\R\R-3.3.3\bin\x64 ;; More information can be found in: https://ccl.northwestern.edu/netlogo/docs/r.html extensions [r] globals [ first_demand ;; create a first demand variable to generate initial inputs and prepare demand history for forecasting mean_demand ;; distributions are used from multiple product category, Therefore there is extreme variation in the demand. The mean_demand is used as a threshold to limit this variation. ] breed [distributors distributor] breed [households household] distributors-own [ order_to_supplier order_from_household total_order_from_household available_stock demand_history ;; a list of all previous demand history group_demand_history ;; list of lists of all distributor demand history forecasting ;; variable to save forecasting result with all statistics forecast ;; variable for final forecasted value forecasting_history ;;save forecasting values to history plot_forecast ;; save previous tick forecasting result for plotting with delay backlog ;; level of unfulfilled demand total_backlog ;; level of unfulfilled demand for all distributors unfulfilled_demand ;; the backlog level is added to unfulfilled demand and is necessary to deliver once available inventory is present safety_stock ;; safety stock level based on previous 7 tick demand average and safety_level set by slider ] households-own [ order_to_distributor distribution_type ;; randomly assigns distribution type to select a distribution function product_quantity ;; the distributions are generated from ratio of sales by day. Therefore, the generated ratio is multiplied by different product quantity, to simulate the variation of consumer shopping habits. ] to setup __clear-all-and-reset-ticks ask patches [set pcolor white] ;; assign the initial demand of all household random-seed 100 create-distributors Distributor_N [ set size 5 set color blue set label (word "Distr." [who] of self " ") set forecast 0 set forecasting_history [] ] create-households Households_N [ set size 1 set color 3 set order_to_distributor 0 set distribution_type [] ] set-default-shape distributors "house two story" set-default-shape households "person" ask turtles [find_patch] end to go reset_variables if ticks = 0 [ set_first__variables ] ifelse consumer_loyalty = FALSE [ create-link interaction_supplier ] [ if ticks = 0 [ create-link ] interaction_supplier ] receive_order_from_supplier select_demand_distribution interaction_household check_mean_demand update_demand_history set_group_history update_inventory if consumer_loyalty = FALSE [ ask links [die] ] tick end ;; randomly assign all distributors and household to individual patch to find_patch setxy random-xcor random-ycor while [any? other turtles-here] [find_patch] end ;; save individual distributor sales history to a list of lists, where the first element in the index of the distributor ;; and the second is a list of demand history of that distributor to set_group_history ask turtles with [ breed = distributors ] [ set group_demand_history (list (list who) (list demand_history)) ] end to set_first__variables select_demand_distribution ask turtles with [breed = households] [ ;; the distribution functions are obtained from ratio of real-data. The purpose of this is to simulate seasonality of the real-world environment. ;; the ratio is then multiplied by the product quantity which household buy. This way the customer is also distinguished by number of people in the household. set product_quantity random (Max_product_quantity - 1) + 1 set order_to_distributor round product_quantity * order_to_distributor if order_to_distributor < 0 [ set order_to_distributor (- order_to_distributor)] let demand [] set demand order_to_distributor set demand round demand create-link ask link-neighbors with [ breed = distributors ] [ set order_from_household demand set total_order_from_household (total_order_from_household + order_from_household) ] ] ask turtles with [ breed = distributors ] [ set first_demand total_order_from_household set demand_history (list (first_demand) (first_demand) (first_demand) (first_demand) (first_demand)) set group_demand_history (list (list who) (list demand_history)) set available_stock first_demand * 3 ] end ;; function to get first n elements of a list to-report take_first [n xs] report sublist xs 0 min list n (length xs) end ;; the order from supplier is added to the available stock. The value is obtained from previous tick, therefore there is a delay of delivery of 1 day to receive_order_from_supplier ask turtles with [ breed = distributors ] [ set safety_stock demand_history set safety_stock reverse safety_stock set safety_stock filter [? != 0] take_first 7 safety_stock set safety_stock (mean safety_stock) * safety_level ifelse available_stock < safety_stock [ set available_stock (available_stock + order_to_supplier + safety_stock) ] [ set available_stock (available_stock + order_to_supplier) ] ] end ;; the demand is forecasted based on individual history or group history of companies. The distributor is ordering supplies 1 tick before the household orders. to interaction_supplier ifelse Share_information = false ;; individual demand forecasting [ let distributors_index n-values count turtles with [breed = distributors] [?] ask turtles with [breed = distributors] [ ;; need to iterate trough individual turtle, otherwise demand_history is added for all distributors not only 1 (list of lists problem) foreach distributors_index [ ;; machine learning/neural network algorithm for individual forecasting ;; clear the R workspace r:clear ;; command to clear console when printing variables use r:eval "print(variable_name)" or show r:get "variable_name", but some formats are not supported by NetLogo r:eval "cat(rep('\n',50))" ;; load extreme machine learning package r:eval "library('elmNN')" r:eval " splitWithOverlap <- function(vec, seg.length) { overlap = seg.length - 1 ; starts = seq(1, length(vec), by=seg.length-overlap); ends = starts + seg.length - 1 ;ends[ends > length(vec)] = length(vec) ;y <- lapply(1:length(starts), function(i) vec[starts[i]:ends[i]]) ;y <- y[unlist(lapply(y, length) == seg.length)] ;y <- as.data.frame(t(as.data.frame(y))) ;rownames(y) <- c() ;colnames(y)[length(colnames(y))] <- 'y' ;return(y) } " ;; put demand_history variable in to R environment and create a sequence column to identify the time series r:put "r_demand_history" [demand_history] of turtle ? r:eval "train <- splitWithOverlap(r_demand_history,4)" ;; in the beginning the neural network when facing with spikes over predicts. Manually we set the over predictions to the mean. The threshold is decided by input r:eval "mean_demand <- mean(r_demand_history)" r:put "mean_threshold" mean_threshold ;; train the extreme machine learning algorithm r:eval "model <- elmtrain(y ~ . , data=train, nhid=1, actfun='sig')" ;; create an empty variable for the next tick prediction r:eval "predict_df <- tail(r_demand_history,3)" r:eval "predict_df <- as.data.frame(t(as.data.frame(predict_df)))" r:eval "predict_df$y <- 0" r:eval "colnames(predict_df) <- c('v1','v2','v3','y')" ;; predict the next tick's demand r:eval "prediction <- predict.elmNN(model, newdata = predict_df)" r:eval "prediction <- as.numeric(prediction)" ;; check if the predicted demand is not above the mean with a certain threshold if mean_limit_status = true [ r:eval "if (prediction > (mean_demand * mean_threshold)) { prediction <- mean_demand * mean_threshold}" ] ;; save the demand to NetLogo variable set forecasting r:get "prediction" ;; save the forecasting results and prepare order from supplier set forecast round forecasting set order_to_supplier forecast if order_to_supplier < 0 [set order_to_supplier 0] ifelse order_to_supplier + unfulfilled_demand < available_stock [ set order_to_supplier 0 ] [ set order_to_supplier (order_to_supplier + unfulfilled_demand - available_stock) ] ] ] ] ;; collaborative demand forecasting (forecast with machine learning from all distributor information, not only individual) [ ;; machine learning/neural network algorithm for individual forecasting ;; clear the R workspace r:clear ;; command to clear console when printing variables use r:eval "print(variable_name)" or show r:get "variable_name", but some formats are not supported by NetLogo r:eval "cat(rep('\n',50))" ;; load extreme machine learning package r:eval "library('elmNN')" r:eval "splitWithOverlap <- function(vec, seg.length) { overlap = seg.length - 1 ; starts = seq(1, length(vec), by=seg.length-overlap); ends = starts + seg.length - 1 ;ends[ends > length(vec)] = length(vec) ;y <- lapply(1:length(starts), function(i) vec[starts[i]:ends[i]]) ;y <- y[unlist(lapply(y, length) == seg.length)] ;y <- as.data.frame(t(as.data.frame(y))) ;rownames(y) <- c() ;colnames(y)[length(colnames(y))] <- 'y' ;return(y) } " r:eval "splitWithOverlap_combine <- function(x, seg.length = 4) { ;for (i in 1:ncol(x)) { ;active_df <- x[i] ;active_df <- t(as.vector(active_df)) ;ts <- splitWithOverlap(active_df,seg.length) ;colnames(ts) <- paste(colnames(x)[i], colnames(ts), sep = '_') ;if (exists('ts_final') == FALSE) { ;ts_final <- ts ;} else { ;ts_final <- cbind(ts_final,ts) ;} ;} ;return(ts_final) ;}" r:eval "create_predict_df <- function(x) { ;for (i in 1:ncol(x)) { ;active_df <- x[i] ;active_df <- tail(active_df,3) ;active_df <- as.data.frame(t(as.vector(active_df))) ;colnames(active_df) <- c('V1','V2','V3') ;active_df$y <- 0 ;colnames(active_df) <- paste(rownames(active_df), colnames(active_df), sep = '_') ;if (exists('df_final') == FALSE) { ;df_final <- active_df ;} else { ;df_final <- cbind(df_final,active_df) ;} ;} ;return(df_final);}" (r:putagent "r_demand_history" turtles with [breed = distributors] "group_demand_history") r:eval "r_demand_history <- as.data.frame(matrix(data = as.vector(unlist(r_demand_history)), nrow = length(unlist(r_demand_history$group_demand_history[[1]])), ncol = length(r_demand_history[[1]])))" r:eval "colnames(r_demand_history) <- r_demand_history[1, ]" r:eval "r_demand_history <- r_demand_history[-1, ] " r:eval "row.names(r_demand_history) <- NULL" r:eval "colnames(r_demand_history) <- paste('Distributor', colnames(r_demand_history), sep = '_')" r:eval "column_names <- colnames(r_demand_history)" r:eval "train <- splitWithOverlap_combine(r_demand_history,4)" ;; in the beginning the neural network when facing with spikes over predicts. Manually we set the over predictions to the mean. The threshold is decided by input r:eval "mean_demand <- mean(colMeans(r_demand_history[1:(ncol(r_demand_history)-1)]))" r:put "mean_threshold" mean_threshold ;; train the extreme machine learning algorithm ;;create a variable of the distributor index let distributors_index n-values count turtles with [breed = distributors] [?] foreach distributors_index [ r:put "active_distributors_index" ? r:eval "active_distributors_index <- as.numeric(active_distributors_index)" r:eval "active_distributor <- paste0('Distributor_',active_distributors_index, '_y')" r:eval "formula <- paste(active_distributor, ' ~ ' ,'.')" r:eval "model <- elmtrain(as.formula(formula) , data=train, nhid=1, actfun='sig')" ;; create an empty variable for the next tick prediction r:eval "col_names <- colnames(train)" r:eval "predict_df <- create_predict_df(r_demand_history)" ;; predict the next tick's demand r:eval "prediction <- predict.elmNN(model, newdata = predict_df)" r:eval "prediction <- as.numeric(prediction)" ;; check if the predicted demand is not above the mean with a certain threshold if mean_limit_status = true [ r:eval "if (prediction > (mean_demand * mean_threshold)) { prediction <- mean_demand * mean_threshold}" ] ;; save the demand to NetLogo variable ask turtle ? [ set forecasting r:get "prediction" ;; save the forecasting results and prepare order from supplier set forecast round forecasting set order_to_supplier forecast if order_to_supplier < 0 [set order_to_supplier 0] ifelse order_to_supplier + unfulfilled_demand < available_stock [ set order_to_supplier 0 ] [ set order_to_supplier (order_to_supplier + unfulfilled_demand - available_stock) ] ] ] ] end ;; pareto distribution (pareto from R package: tdistrplus) to-report random-pareto [alpha mm] report mm / ( random-float 1 ^ (1 / alpha) ) end ;; lognormal distribution (lnorm from R package: tdistrplus) to-report log-normal [mu sigma] let beta ln (1 + ((sigma ^ 2) / (mu ^ 2))) let x exp (random-normal (ln (mu) - (beta / 2)) sqrt beta) report x end ;; log-logistics distribution (llogis from R package: tdistrplus) to-report random-loglogistics [alpha beta] let r random-float 1 let q r / (1 - r) let x alpha * exp (ln (q) / beta) report x end ;; burr distribution (burr from R package: tdistrplus) to-report random-burr [c k] let r random-float 1 let q exp ( - ln(r) / k) - 1 ;;if q < 0 [ set q (- q) ] let x exp ( ln(q) / c ) report x end to select_demand_distribution ask turtles with [breed = households] [ ;; the household decides how much to order based on distributions fitted on real-data. ;; if the market type is perfect competition it means that there are distributors who are not involved in sharing information ;; therefore some household randomly buy products from other distributors ;; if the market is oligopoly it means that all distributors from the market are involved in sharing information if market_type = "Oligopoly" [set distribution_type random (7 - 1) + 1] if market_type = "Perfect competition" [set distribution_type random (28 - 1) + 1] if distribution_type = 1 [ set order_to_distributor random-pareto 8.021208 10.829160 ] if distribution_type = 2 [ set order_to_distributor random-loglogistics 0.5279758 1.0251284 ] if distribution_type = 3 [ set order_to_distributor random-burr 0.8406488 2.1144042] if distribution_type = 4 [ set order_to_distributor random-burr 0.888256 1.807287] if distribution_type = 5 [ set order_to_distributor random-pareto 8.021208 10.829160 ] if distribution_type = 6 [ set order_to_distributor random-loglogistics 0.5594916 0.9898947 ] if distribution_type = 7 [ set order_to_distributor log-normal 0.5594916 0.9898947 ] if distribution_type > 7 [ set order_to_distributor 0 ] ] end to create-link ask turtles with [breed = households] [create-link-with one-of other turtles with [ breed = distributors ] ] end ;;households randomly selects a distributor and sends him an order based on demand distribution type to interaction_household ask turtles with [breed = households] [ ;; the distribution functions are obtained from ratio of real-data. The purpose of this is to simulate seasonality of the real-world environment. ;; the ratio is then multiplied by the product quantity which household buy. This way the customer is also distinguished by number of people in the household. set product_quantity random (Max_product_quantity - 1) + 1 if order_to_distributor < 0 [ set order_to_distributor (- order_to_distributor)] set order_to_distributor round product_quantity * order_to_distributor let demand [] set demand order_to_distributor set demand round demand ask link-neighbors with [ breed = distributors ] [ set order_from_household demand set total_order_from_household (total_order_from_household + order_from_household) ] ] end ;; determines the mean demand to check_mean_demand if mean_limit_status = True [ ask turtles with [breed = distributors] [ let distributors_index n-values count turtles with [breed = distributors] [?] foreach distributors_index [ r:put "mean_demand" [demand_history] of turtle ? r:eval "mean_demand <- mean(mean_demand)" r:eval "mean_demand <- as.numeric(mean_demand)" set mean_demand r:get "mean_demand" if mean_limit_status = true [ if total_order_from_household > mean_demand * mean_threshold [set total_order_from_household mean_demand * mean_threshold] ] ] ] ] end ;; send orders to household by subtracting total order from household from available stock to update_inventory ask turtles with [breed = distributors] [ ifelse unfulfilled_demand != 0 [ set available_stock (available_stock - total_order_from_household - unfulfilled_demand) set unfulfilled_demand 0 ] [ set available_stock (available_stock - total_order_from_household) ] ;; evaluate the unfulfilled orders if the stock level was negative if available_stock < 0 [ set backlog (- available_stock) set total_backlog total_backlog + backlog set unfulfilled_demand total_backlog ] ] end ;; add a total demand variable to the list of demand history to update_demand_history ask turtles with [breed = distributors] [ set demand_history lput total_order_from_household demand_history set forecasting_history lput forecast forecasting_history if ticks != 0 [ set plot_forecast item (length(forecasting_history) - 2) forecasting_history ] ] end ;; reset save variables to reset_variables ask turtles with [ breed = distributors ] [ set total_order_from_household 0 set total_backlog 0 ] end

There is only one version of this model, created almost 5 years ago by Shrinidhi KR KR.

## Attached files

No files

This model does not have any ancestors.

This model does not have any descendants.