Petrol Panic Buying Dynamics
Do you have questions or comments about this model? Ask them here! (You'll first need to log in.)
Petrol Panic Buying Simulation
Extended from the original panic buying model by Steve D'Alessandro and Hume Winzar
WHAT IS IT?
This model simulates panic buying behaviour in the context of petrol/fuel shortages. Unlike the original model focused on general retail goods, this version incorporates fuel-specific dynamics including:
- Cars as agents with fuel tanks that deplete over time
- Petrol stations with limited stock and variable pricing
- National fuel reserves measured in days of supply (e.g., 90 days)
- Price dynamics influenced by reserve levels
- Multiple panic triggers including stock shortages, high prices, and low reserves
HOW IT WORKS
Agents (Cars/Drivers)
Each car has: - A fuel tank (0-100%) that depletes each tick - Perception of nearby fuel availability and prices - Memory of past stock levels - Price sensitivity - Panic state (calm or panicked)
Environment
- Petrol Stations: Patches that hold fuel stock and set prices (marked with yellow petrol pump symbols)
- National Reserves: Global fuel reserve measured in days of supply remaining (e.g., "90 days of fuel")
- Price Mechanism: Prices increase when reserves drop below 30% of initial level
Panic Mechanisms
- Shortage-Based Panic: Cars become panicked when perceived stock falls below threshold
- Price-Based Panic: High prices trigger panic in price-sensitive drivers
- Reserves-Based Panic: Low national reserves (measured in days) cause widespread panic
- Social Contagion: Panicked drivers influence others nearby
- Empty Tank Panic: Cars with very low fuel become panicked
Panic Behaviour
- Panicked drivers attempt to fill tanks completely
- May hoard fuel by buying at multiple stations
- Spread panic to other drivers through social influence
INTERFACE CONTROLS
Sliders
Number of Cars (50-500): Total number of car agents in the simulation
Petrol Stations (10-80): Number of fuel stations in the world
Initial Stock per Station (200-1000 litres): Starting fuel inventory at each station
Average Startup Price ($1.00-$3.00/L): Average base price per liter across all stations (default $1.65, actual prices vary ±$0.15 around this average)
Initial Panic Size (0-20): Number of drivers who start panicked
Randomness (1-50): Adds variation to panic threshold decisions
Stock Perception Radius (2-15): How far cars can perceive fuel availability
Communication Radius (2-10): Distance over which panic can spread socially
Restock Rate (1-28 days): How often stations receive new fuel deliveries
Memory (1-28 days): How many days of stock history cars remember
National Reserve Days (0-300 days): National fuel reserves in days of supply (typical: 90 days)
Price Volatility (0.1-2.0): How much prices fluctuate with reserve levels
Reserves Panic Threshold (10-50%): Reserve level (as % of initial) below which panic spreads
Panic Contagion Rate (1-20%): Probability of panic spreading between cars
Switches
Shortage Makes Panic: Stock shortages can trigger panic
Panic Makes Panic: Panicked drivers can influence others
Panic Hoarding: Panicked drivers buy out multiple stations
Memory Moderates Panic: Historical stock memory affects panic decisions
Price Triggers Panic: High prices can trigger panic
Reserves Trigger Panic: Low national reserves (in days) can trigger panic
THINGS TO NOTICE
- How quickly panic spreads when reserve days drop below threshold
- The relationship between price increases and panic levels
- How memory of past availability moderates panic responses
- The clustering of panicked cars around depleted stations
- The feedback loop between panic, hoarding, and actual shortages
- How "days of reserves" provides clear public communication metric
THINGS TO TRY
Supply Shock Scenario: Set reserve-days to 30, restock rate to 14 days, observe cascading panic
Price Sensitivity Test: Enable price-triggers-panic, set high price-volatility, watch price-driven panic
Memory Effects: Compare runs with memory = 2 days vs 28 days to see how historical awareness affects outcomes
Communication Intensity: Vary communication-radius and panic-contagion-rate to model social media effects
Reserve Communication: Toggle reserves-trigger-panic to see impact of public awareness of "days remaining"
Strategic Reserve: Start with 90 days (typical government reserve), watch how depletion triggers panic
EXTENDING THE MODEL
Possible Extensions
Media Influence: Add global news broadcasts that increase panic across all agents
Station Types: Differentiate between major brands and independent stations with different pricing strategies
Government Intervention: Model strategic reserve releases or price controls
Electric Vehicles: Add EVs that don't need petrol but may trigger panic among conventional car owners
Regional Variations: Model different geographic areas with varying access to fuel infrastructure
Queue Formation: Explicit modeling of queuing behaviour at popular stations
NETLOGO FEATURES
This model uses: - Custom car shapes for agents - Dynamic price calculations based on reserve days - Memory vectors with decay weights for temporal discounting - Multiple panic triggering mechanisms - Spatial perception using in-radius operations - Real-world reserve metrics (days of supply)
RELATED MODELS
Original Panic Buying Simulation by D'Alessandro & Winzar (2022) for COVID-19 consumer goods
CREDITS AND REFERENCES
Model adapted for petrol context (2024) based on: D'Alessandro, S. & Winzar, H. (2022). Don't panic! An agent-based model approach to understanding panic buying. ANZMAC Conference.
THEORETICAL BACKGROUND
National Reserves in Days
Governments typically maintain strategic petroleum reserves measured in days of import cover or days of consumption. For example: - International Energy Agency (IEA) recommends 90 days - United States: Strategic Petroleum Reserve provides ~140 days (historically) - European Union: 90 days mandatory for member states - Australia: Maintains reserves to meet IEA obligations
Expressing reserves in "days of supply" provides: - Clear public communication metric - Intuitive understanding of crisis severity - Direct comparability across countries - Basis for panic threshold decisions
Recent Examples
- 2021 Colonial Pipeline hack (US): Public feared days of shortage
- 2022 fuel shortages following Russia-Ukraine conflict (Europe): Reserve day counts widely reported
- 2021 UK fuel crisis: Media reported "days until stations run dry"
When reserve days drop below ~30 days, public panic typically intensifies significantly.
Comments and Questions
;; Petrol Panic Buying Simulation ;; Extended from panic buying model by Steve D'Allessandro and Hume Winzar ;; Adaptation for petrol/fuel context globals [ initial-mean-stock ;; at startup - average fuel perceptions of all agents mean_stock ;; at each iteration - average fuel perceptions of all agents mean_price ;; average petrol price across stations decay ;; a vector of decay coefficients: if memory slider = 5 then decay = [ 1 2 3 4 5 ] decay-vector ;; for rescaling the memory decay values daily-consumption ;; daily fuel consumption rate across all vehicles reserve-days ;; current reserves expressed in days of supply initial-reserve-days ;; starting reserve level in days ;; Media influence ticks-since-broadcast ;; time since last news broadcast total-broadcasts ;; count of news broadcasts triggered ;; Queue tracking total-queue-length ;; sum of all queue lengths across stations avg-queue-length ;; average queue length per station ;; Station outages station-outages-percent ;; percentage of stations with zero stock ;; Government interventions purchase-limit-active? ;; whether purchase limits are in effect price-control-active? ;; whether price controls are active coordinated-messaging? ;; whether gov messaging is reducing panic reserves-released ;; total reserve days released by government strategic-reserves-available ;; remaining strategic reserves that can be released (in days) ;; Data recording historical-data ;; list of data records for export ] breed [station-markers station-marker] ;; visual markers for petrol stations turtles-own [ panicked? ;; if true, the car/driver is panicked stock-perception ;; average of fuel in surrounding stations price-perception ;; average price perception stock-memory ;; a list record of last few stock levels weighted-memory ;; a vector of length memory: most recent stock perceptions have greater salience sum-weighted-memory ;; a single score: the sum of weighted-memory fuel-tank ;; current fuel level in tank (0-100%) fuel-consumption ;; rate of fuel consumption per tick price-sensitivity ;; how sensitive driver is to price changes in-queue? ;; whether car is currently waiting in queue queue-time ;; how long car has been in queue daily-fuel-purchased ;; total fuel purchased today (for purchase limits) last-purchase-day ;; last day when purchase counter was reset ] patches-own [ stock ;; available petrol for purchase (in litres) stock-order ;; usual resupply brings stock up to usual level base-price ;; base price per litre current-price ;; current price (affected by reserves and demand) is-station? ;; whether this patch is a petrol station queue-length ;; number of cars waiting at this station ] to setup clear-all setup-memory setup-patches setup-reserves initialize-interventions update-patches set initial-mean-stock mean [stock] of patches with [is-station?] setup-cars reset-ticks update-turtles end to setup-memory set decay n-values memory [ i -> i + 1 ] ;; creates a vector of values 1 thru size of memory set decay-vector n-values memory [ i -> ( reduce + decay ) ] ;; a vector of length memory all with the same value end to setup-reserves set initial-reserve-days national-reserve-days set reserve-days national-reserve-days set daily-consumption 0 set strategic-reserves-available strategic-reserves-pool end to setup-cars set-default-shape turtles "car" create-turtles number-of-cars [ setxy (random-xcor * 0.95) (random-ycor * 0.95) set panicked? false set color blue set stock-memory ( list ( initial-mean-stock ) ) repeat ( memory - 1 ) [ set stock-memory lput initial-mean-stock stock-memory ] set fuel-tank 50 + random 50 set fuel-consumption 1 + random 2 set price-sensitivity 0.5 + random-float 0.5 set in-queue? false set queue-time 0 set daily-fuel-purchased 0 set last-purchase-day 0 ] ;; Panic the correct percentage of cars let panic-count round (initial-panic-size / 100 * number-of-cars) ask n-of panic-count turtles [ become-panicked ] end to setup-patches ask patches [ set is-station? false set stock 0 set pcolor green - 2 ;; road color set queue-length 0 ] ;; Create petrol stations (fewer, larger stock areas) ask n-of petrol-stations patches [ set is-station? true set stock (initial-stock-per-station + random 50) set stock-order stock set base-price (average-startup-price - 0.15) + random-float 0.30 ;; base price varies ±15 cents around average set current-price base-price set pcolor scale-color orange stock 0 (initial-stock-per-station * 2) set queue-length 0 ;; Create visual station marker sprout-station-markers 1 [ set shape "petrol-pump" set color yellow set size 2.5 ] ] end to go ;; Make sure setup has been run first if not is-list? historical-data [ setup ] if ticks >= max-days [ stop ] ;; Stop if reserves reach zero (if switch is on) if stop-at-zero-reserves and reserve-days <= 0 [ print (word "Simulation stopped: Reserves depleted at day " ticks) stop ] set ticks-since-broadcast ticks-since-broadcast + 1 update-reserves update-patches update-turtles consume-fuel update-queues resupply spread-panic record-data ;; Record data each tick for export tick ; AUTO-TRIGGER INTERVENTIONS ; Messaging if ticks = messaging-start-day and auto-trigger-messaging [ coordinated-gov-messaging ] ; Purchase limits if ticks = purchase-limit-start-day and auto-trigger-limits [ implement-purchase-limits ] ; Reserve release if ticks = reserve-release-start-day and auto-trigger-release [ release-strategic-reserves ] ; Price controls if ticks = price-control-start-day and price-controls-active [ ; Price controls are already implemented in your model ; This just documents when they start ] ; News broadcasts if auto-trigger-broadcasts [ let broadcast-list read-from-string broadcast-days if member? ticks broadcast-list [ news-broadcast ] ] end to record-data ;; Record current state for later export let panic-pct (count turtles with [not (breed = station-markers) and panicked?] / number-of-cars * 100) let data-row (list ticks panic-pct mean_stock reserve-days mean_price avg-queue-length station-outages-percent total-broadcasts purchase-limit-active? price-control-active? coordinated-messaging?) set historical-data lput data-row historical-data end to become-panicked ;; turtle procedure set panicked? true set color red set size 1.5 ;; panicked cars appear larger end to initialize-interventions set ticks-since-broadcast 0 set total-broadcasts 0 set total-queue-length 0 set avg-queue-length 0 set station-outages-percent 0 set purchase-limit-active? false set price-control-active? false set coordinated-messaging? false set reserves-released 0 set historical-data [] ;; empty list to start end to news-broadcast ;; Simulate a breaking news event that triggers panic ;; Government messaging reduces the effectiveness of media panic set total-broadcasts total-broadcasts + 1 set ticks-since-broadcast 0 ;; Government messaging reduces media influence effectiveness by 50% let effective-media-influence media-influence-strength if coordinated-messaging? = true [ set effective-media-influence media-influence-strength * 0.5 ] ;; News affects a percentage of calm drivers based on effective media influence let affected-count floor (effective-media-influence * count turtles with [not (breed = station-markers) and not panicked?] / 100) ask n-of affected-count turtles with [not (breed = station-markers) and not panicked?] [ become-panicked ] end to update-queues ;; Update queue lengths at each station ask patches with [is-station?] [ set queue-length count turtles with [not (breed = station-markers) and in-queue?] in-radius 2 ] ;; Calculate global queue statistics set total-queue-length sum [queue-length] of patches with [is-station?] ifelse petrol-stations > 0 [ set avg-queue-length total-queue-length / petrol-stations ] [ set avg-queue-length 0 ] ;; Calculate station outages percentage let stations-out-of-stock count patches with [is-station? and stock <= 0] ifelse petrol-stations > 0 [ set station-outages-percent (stations-out-of-stock / petrol-stations * 100) ] [ set station-outages-percent 0 ] ;; Cars in queues trigger panic in observers if queues-trigger-panic = true [ ask turtles with [not (breed = station-markers) and not panicked?] [ let nearby-queue-length sum [queue-length] of patches with [is-station?] in-radius 5 if nearby-queue-length > 3 ;; seeing 3+ cars queued triggers panic [ if random 100 < 10 ;; 10% chance per tick when seeing queues [ become-panicked ] ] ] ] end to release-strategic-reserves ;; Government releases strategic reserves into market (reduces overall reserve days) ;; This captures the tradeoff: using reserves to calm panic but depleting overall supply ;; Can only release what's available in the strategic reserve pool ifelse strategic-reserves-available >= reserve-release-days [ ;; Enough strategic reserves available - release full amount let release-amount reserve-release-days set reserve-days reserve-days - release-amount set reserves-released reserves-released + release-amount set strategic-reserves-available strategic-reserves-available - release-amount print (word "Released " release-amount " days of strategic reserves. " strategic-reserves-available " days remaining in storage.") ] [ ;; Not enough strategic reserves - release only what's left ifelse strategic-reserves-available > 0 [ let release-amount strategic-reserves-available set reserve-days reserve-days - release-amount set reserves-released reserves-released + release-amount set strategic-reserves-available 0 print (word "Released final " release-amount " days of strategic reserves. Storage now EMPTY.") ] [ ;; No strategic reserves left print "ERROR: No strategic reserves remaining! Cannot release more." ] ] end to implement-purchase-limits ;; Toggle purchase limits on/off set purchase-limit-active? not purchase-limit-active? end to implement-price-controls ;; Toggle price controls on/off set price-control-active? not price-control-active? ifelse price-control-active? [ ;; Cap prices at 15% above base (allows some price signaling while preventing gouging) ask patches with [is-station?] [ ifelse reserve-days < (initial-reserve-days * 0.3) [ let market-price base-price * (1 + ((initial-reserve-days - reserve-days) / initial-reserve-days) * price-volatility) set current-price min (list market-price (base-price * 1.15)) ] [ ;; If reserves are still adequate, keep at base price set current-price base-price ] ] ] [ ;; Deactivating controls - restore market pricing ask patches with [is-station?] [ if reserve-days < (initial-reserve-days * 0.3) [ set current-price base-price * (1 + ((initial-reserve-days - reserve-days) / initial-reserve-days) * price-volatility) ] ] ] end to coordinated-gov-messaging ;; Government coordinated messaging reduces panic contagion set coordinated-messaging? not coordinated-messaging? end to export-data ;; Export all historical simulation data to CSV file let default-filename (word "panic_buying_data_" number-of-cars "cars_" petrol-stations "stations.csv") ;; Let user choose where to save the file let filename user-new-file ;; If user cancelled, exit if filename = false [ print "Export cancelled by user" stop ] ;; Delete old file if exists carefully [ file-delete filename ] [ ] ;; Write header file-open filename file-print "day,panic_percentage,avg_stock,reserve_days,avg_price,avg_queue_length,station_outages_percent,total_broadcasts,purchase_limit_active,price_control_active,coordinated_messaging" ;; Write all historical data foreach historical-data [ data-row -> file-print (word (item 0 data-row) "," ;; day (item 1 data-row) "," ;; panic_percentage (item 2 data-row) "," ;; avg_stock (item 3 data-row) "," ;; reserve_days (item 4 data-row) "," ;; avg_price (item 5 data-row) "," ;; avg_queue_length (item 6 data-row) "," ;; station_outages_percent (item 7 data-row) "," ;; total_broadcasts (item 8 data-row) "," ;; purchase_limit_active (item 9 data-row) "," ;; price_control_active (item 10 data-row)) ;; coordinated_messaging ] file-close print (word "Exported " length historical-data " days of data to: " filename) end to update-reserves ;; Calculate daily consumption rate (total fuel consumed per day) set daily-consumption sum [fuel-consumption] of turtles with [not (breed = station-markers)] ;; Reserves deplete based on overall demand and panic level let panic-multiplier 1 + (count turtles with [not (breed = station-markers) and panicked?] / number-of-cars) let reserve-depletion (daily-consumption * panic-multiplier) / (initial-stock-per-station * petrol-stations) ;; Update reserve days (depletes faster when panic buying occurs) set reserve-days reserve-days - reserve-depletion if reserve-days < 0 [ set reserve-days 0 ] ;; Low reserves trigger price increases if reserve-days < (initial-reserve-days * 0.3) [ ask patches with [is-station?] [ let market-price base-price * (1 + ((initial-reserve-days - reserve-days) / initial-reserve-days) * price-volatility) ;; Apply price controls cap if active (max 15% increase), otherwise use market price ifelse price-control-active? [ set current-price min (list market-price (base-price * 1.15)) ] [ set current-price market-price ] ] ] end to spread-panic if shortage-makes-panic = true [ ask turtles with [not (breed = station-markers)] [ ifelse memory-moderates-panic = true [ set weighted-memory ( map * stock-memory decay) set weighted-memory ( map / weighted-memory decay-vector ) set sum-weighted-memory reduce + weighted-memory if sum-weighted-memory < ( initial-mean-stock * (( random randomness) / 100 )) [ become-panicked ] ] [ if stock-perception < ( initial-mean-stock * (( random randomness) / 100 )) [ become-panicked ] ] ] ] ;; Price panic - high prices can trigger panic if price-triggers-panic = true [ ask turtles with [not (breed = station-markers)] [ if price-perception > (mean_price * price-panic-threshold * price-sensitivity) [ become-panicked ] ] ] ;; Reserves panic - awareness of low national reserves (measured in days) if reserves-trigger-panic = true [ if reserve-days < (initial-reserve-days * reserves-panic-threshold / 100) [ ask turtles with [not (breed = station-markers)] [ if random 100 < 5 ;; 5% chance per tick when reserves low [ become-panicked ] ] ] ] if panic-makes-panic = true [ ;; Coordinated messaging reduces panic contagion rate by 50% let effective-contagion-rate panic-contagion-rate if coordinated-messaging? = true [ set effective-contagion-rate panic-contagion-rate * 0.5 ] ask turtles with [not (breed = station-markers) and panicked?] [ ask turtles with [not (breed = station-markers)] in-radius communication-radius [ if random 100 < effective-contagion-rate [ become-panicked ] ] ] ] end to consume-fuel ask turtles with [not (breed = station-markers)] [ set fuel-tank fuel-tank - fuel-consumption ;; Cars with very low fuel become panicked if fuel-tank < 20 and not panicked? [ if random 100 < 30 [ become-panicked ] ] ;; Refuel tank if possible if fuel-tank < 100 [ let available-fuel [stock] of patch-here ifelse available-fuel > 0 [ let fuel-needed min (list (100 - fuel-tank) available-fuel) set fuel-tank fuel-tank + fuel-needed ask patch-here [ if is-station? [ set stock stock - fuel-needed ] ] ] [ ;; No fuel at current location, move towards nearest station let nearest-station min-one-of patches with [is-station? and stock > 0] [distance myself] if nearest-station != nobody [ face nearest-station forward 1 ] ] ] ] end to resupply if ticks mod restock-rate = 0 [ ask patches with [is-station?] [ ;; Resupply depends on national reserves (days available) let supply-multiplier min (list 1.0 (reserve-days / initial-reserve-days)) set stock stock + (stock-order * supply-multiplier * 0.5) if stock > stock-order [ set stock stock-order ] set pcolor scale-color orange stock 0 (initial-stock-per-station * 2) ] ] end to update-patches set mean_stock mean [stock] of patches with [is-station?] set mean_price mean [current-price] of patches with [is-station?] ask patches with [is-station?] [ set pcolor scale-color orange stock 0 (initial-stock-per-station * 2) ] end to update-turtles ask turtles with [not (breed = station-markers)] [ ;; Move if no fuel at current location if ( [stock] of patch-here <= 0 ) or ( not [is-station?] of patch-here ) [ set heading random 360 forward 1 ] ;; Update perceptions let nearby-stations patches with [is-station?] in-radius stock-perception-radius ifelse any? nearby-stations [ set stock-perception mean [stock] of nearby-stations set price-perception mean [current-price] of nearby-stations ] [ set stock-perception 0 set price-perception mean_price ] set stock-memory lput stock-perception stock-memory if length stock-memory > memory [ set stock-memory remove-item 0 stock-memory ] ] ;; Update queue status ask turtles with [not (breed = station-markers)] [ ifelse [is-station?] of patch-here and fuel-tank < 90 [ set in-queue? true set queue-time queue-time + 1 ] [ set in-queue? false set queue-time 0 ] ] ;; Reset daily purchase counter at start of each new day ask turtles with [not (breed = station-markers)] [ if ticks != last-purchase-day [ set daily-fuel-purchased 0 set last-purchase-day ticks ] ] ;; Regular (non-panicked) cars buy normal amounts ask turtles with [not (breed = station-markers) and not panicked?] [ if fuel-tank < 80 ;; refuel if below 80% [ let desired-amount 20 ;; Apply daily purchase limit if active let purchase-amount desired-amount if purchase-limit-active? [ ;; Check if we've hit daily limit if daily-fuel-purchased >= purchase-limit-amount [ set purchase-amount 0 ] ;; Already bought daily limit ;; Otherwise buy what we can up to remaining allowance if daily-fuel-purchased < purchase-limit-amount [ set purchase-amount min (list desired-amount (purchase-limit-amount - daily-fuel-purchased)) ] ] ;; Make purchase if allowed if purchase-amount > 0 [ ask patch-here [ if is-station? and stock >= purchase-amount [ set stock ( stock - purchase-amount) ask myself [ set daily-fuel-purchased daily-fuel-purchased + purchase-amount ] ] ] ] ] ] ;; Panicked cars try to fill up completely and hoard ask turtles with [not (breed = station-markers) and panicked?] [ ifelse panic-hoarding = true [ ;; With daily limits, panicked cars are capped at daily allowance ifelse purchase-limit-active? [ ;; Check remaining daily allowance let remaining-allowance purchase-limit-amount - daily-fuel-purchased if remaining-allowance > 0 [ ask patch-here [ if is-station? [ let actual-purchase min (list remaining-allowance stock) set stock max (list 0 (stock - actual-purchase)) ask myself [ set daily-fuel-purchased daily-fuel-purchased + actual-purchase ] ] ] ] ] [ ;; Without limits, buy everything at current station and neighbors ask patch-here [ if is-station? [ ask neighbors with [is-station?] [ set stock 0 ] set stock 0 ] ] ] ] [ ;; Just buy maximum at current station (60L default for panicked without hoarding) let desired-amount 60 ;; Apply daily purchase limit if active let purchase-amount desired-amount if purchase-limit-active? [ let remaining-allowance purchase-limit-amount - daily-fuel-purchased set purchase-amount min (list desired-amount remaining-allowance) ] if purchase-amount > 0 [ ask patch-here [ if is-station? [ let actual-purchase min (list purchase-amount stock) set stock max (list 0 (stock - actual-purchase)) ask myself [ set daily-fuel-purchased daily-fuel-purchased + actual-purchase ] ] ] ] ] ] end
There are 2 versions of this model.
Attached files
| File | Type | Description | Last updated | |
|---|---|---|---|---|
| Petrol Panic Buying Dynamics.png | preview | Preview for 'Petrol Panic Buying Dynamics' | 26 days ago, by Steven D'Alessandro | Download |
This model does not have any ancestors.
This model does not have any descendants.
Download this model