Consumer values and diets ABM

No preview image

1 collaborator

Default-person Natalie Davis (Author)

Tags

(This model has yet to be categorized with any tags)
Visible to everyone | Changeable by the author
Model was written in NetLogo 6.2.1 • Viewed 159 times • Downloaded 28 times • Run 0 times
Download the 'Consumer values and diets ABM' modelDownload this modelEmbed this model

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


Comments and Questions

Please start the discussion about this model! (You'll first need to log in.)

Click to Run Model

;--------------------------------------------------------------------------------------;
;    This model is free software: you can redistribute it and/or modify                ;
;    it under the terms of the GNU General Public License as published by the          ;
;    Free Software Foundation, either version 3 of the License, or (at your option)    ;
;    any later version.                                                                ;
;                                                                                      ;
;    This program is distributed in the hope that it will be useful, but               ;
;    WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY        ;
;    or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License           ;
;    for more details.                                                                 ;
;                                                                                      ;
;    For the full GNU General Public License, please see                               ;
;    .                                                  ;
;                                                                                      ;
;    Author: Natalie Davis                                                             ;
;--------------------------------------------------------------------------------------;

extensions [ table rnd csv ]

breed [ consumers consumer ]
breed [ diets diet ]
undirected-link-breed [ consumer-diets consumer-diet ]
undirected-link-breed [ social-contacts social-contact ]

globals [
  this-seed
]

consumers-own [
  cost-motivation
  taste-motivation
  ethics-motivation
  health-motivation
  social-information-adherence
  contacts-this-timestep
  household-income
  max-willingness-to-spend
  household-size
  satisfaction-threshold
  current-diet
  current-diet-utility-score
  max-diet-utility-score
  min-diet-utility-score
  perceptions-set? ; for set up only
  household-set? ; for set up only
]

diets-own [
  name
  cost
]

consumer-diets-own [
  diet-name
  financially-feasible?
  perceived-cost
  perceived-taste
  perceived-ethics
  perceived-health
  times-consumed
]

social-contacts-own [
  is-household-member?
  is-close-friend?
  link-strength
  similarity
  ; trust
  no-of-interactions
  interacted-this-timestep?
]

to setup
  clear-all
  reset-ticks
  ; Set up seed for writing, re-creating output if needed
  ifelse my-seed != 0 and round my-seed = my-seed [ set this-seed my-seed ] [ set this-seed new-seed ]
  random-seed this-seed
  make-consumers
  make-diets
  make-consumer-diet-links
  make-social-network
  record-consumer-baseline-data
  record-run-params
end 

to go
  ask social-contacts [
    set interacted-this-timestep? false
  ]
  ask consumers [
    set contacts-this-timestep 0
    update-diet-utility
    choose-diet
    if social-interaction? [
      discuss-with-social-network
    ]
    update-taste-perception
  ]
  ; Record data for this timestep
  record-consumer-data
  record-network-data
  if network-structural-change? [
    ; Update network structure for next timestep
    update-links
  ]
  tick
end 

to choose-diet
  ; Choose new if normalized utility of current diet is below satisfaction threshold or if max. utility score is 0 (i.e. at initialization)
  let choose-new? ifelse-value (max-diet-utility-score = 0 or current-diet-utility-score < satisfaction-threshold)
  [ true ][ false ]
  if choose-new? [
    ; Evaluate each diet and assign score
    let diet-scores table:make
    let diet-score-sum 0
    let max-utility-score max-diet-utility-score
    ; If min. diet utility score = 0 (e.g. at initialization), set min. utility score temp variable here to 100 to force update
    let min-utility-score ifelse-value min-diet-utility-score > 0 [ min-diet-utility-score ] [ 100 ]
    ask my-consumer-diets [
      ; diet score - product of constraints (0/1) and motivations * perceptions, normalized to [0, 1] for weighting in selection (with infeasible diets automatically = 0)
      let diet-score ifelse-value include-health-motivation? [
        ifelse-value include-ethics-motivation? [
          financially-feasible? * (((
            [ cost-motivation ] of myself * perceived-cost +
            [ taste-motivation ] of myself * perceived-taste +
            [ ethics-motivation ] of myself * perceived-ethics +
            [ health-motivation ] of myself * perceived-health
          ) + 1) / 2)
        ] [
          financially-feasible? * (((
            [ cost-motivation ] of myself * perceived-cost +
            [ taste-motivation ] of myself * perceived-taste +
            [ health-motivation ] of myself * perceived-health
          ) + 1) / 2)
        ]
      ] [
        financially-feasible? * (((
          [ cost-motivation ] of myself * perceived-cost +
          [ taste-motivation ] of myself * perceived-taste
        ) + 1) / 2)
      ]
      table:put diet-scores diet-name diet-score
      ; Update min, max, total utilities
      if diet-score > max-utility-score [ set max-utility-score diet-score ]
      if diet-score < min-utility-score [ set min-utility-score diet-score ]
      set diet-score-sum diet-score-sum + diet-score
    ]
    ; Update consumer's min and max diet utility scores from this round of calculating utilities
    set max-diet-utility-score max-utility-score
    set min-diet-utility-score min-utility-score
    ; Check if no diets scored >= 0 (i.e. none are suitable)
    ifelse (max table:values diet-scores = 0) [
      ; If no diets are suitable, choose least expensive diet
      set current-diet one-of diets with-min [ cost ]
      set current-diet-utility-score 0
    ] [
      ; If all diets scored equally (unlikely), choose least expensive diet
      ifelse max-diet-utility-score - min-diet-utility-score = 0 [
        set current-diet one-of diets with-min [ cost ]
        set current-diet-utility-score max-diet-utility-score
      ] [
        ; Choose diet using 'roulette wheel' selection with score as weight (probability of being selected)
        let selection rnd:weighted-one-of-list table:to-list diet-scores [ [ a ] -> last a / diet-score-sum ]
        ; Set current diet of consumer to diet named in selection, save utility score for selected diet
        set current-diet one-of diets with [ name = first selection ]
        set current-diet-utility-score last selection
      ]
    ]
  ]
  ask my-consumer-diets with [ diet-name = [ name ] of [ current-diet ] of myself ] [ set times-consumed times-consumed + 1 ]
end 

to discuss-with-social-network
  ; Sort possible social contacts by link strength, then similarity
  let sorted-social-contacts sort-by [ [ x y ] ->
    ([ link-strength ] of x > [ link-strength ] of y) or
    ([ link-strength ] of x >= [ link-strength ] of y and [ similarity ] of x > [ similarity ] of y) or
    ([ link-strength ] of x = [ link-strength ] of y and [ similarity ] of x > [ similarity ] of y)
    ; only include other social contacts who have not used up all of their social interactions for timestep
  ] my-social-contacts with [ [ contacts-this-timestep ] of (ifelse-value end1 = myself [ end2 ] [ end1 ]) < max-contacts-per-timestep ]
  ; Each consumer tests if they will contact each of the other agents on their list
  while [ contacts-this-timestep < max-contacts-per-timestep and length sorted-social-contacts > 0 ] [
    let next-contact first sorted-social-contacts
    let alter ifelse-value [ end1 ] of next-contact = self [ [ end2 ] of next-contact ] [ [ end1 ] of next-contact ]
    ifelse sum [ link-strength ] of my-social-contacts = 0 or (sum [ link-strength ] of my-social-contacts - [ link-strength ] of next-contact) = 0 [
      ; If I don't have anyone (else) in particular I would interact with...
      if ([ similarity ] of next-contact > random-float 1) [
        exchange-influence alter
        set contacts-this-timestep contacts-this-timestep + 1
        ask next-contact [ set no-of-interactions no-of-interactions + 1 ]
      ]
    ] [
      if (
        ; or we're (1) connected already
        ([ link-strength ] of next-contact > 0 and ([ link-strength ] of next-contact / (sum [ link-strength ] of my-social-contacts - [ link-strength ] of next-contact) > random-float 1)) or
        ; or (2) completely unknown to each other and our friends, but similar enough that we might cross paths...
        ((([ similarity ] of next-contact / sum [ link-strength ] of my-social-contacts) > random-float 1)) ) [
        ; then we exchange influence
        exchange-influence alter
        set contacts-this-timestep contacts-this-timestep + 1
        ask next-contact [
          set no-of-interactions no-of-interactions + 1
          set interacted-this-timestep? true
        ]
      ]
    ]
    set sorted-social-contacts remove next-contact sorted-social-contacts
  ]

  ; Default interaction with household members
  ask my-social-contacts with [ is-household-member? ] [
    let hh-member ifelse-value end1 = myself [ end2 ] [ end1 ]
    ask myself [ exchange-influence hh-member ]
    set no-of-interactions no-of-interactions + 1
  ]
  set contacts-this-timestep contacts-this-timestep + count social-contacts with [ is-household-member? ]
end 

to exchange-influence [ friend ]
  ; Store perceptions as temporary variables to allow for synchronous updating
  let current-diet-this-friend [ current-diet ] of friend
  let current-diet-myself current-diet
  let friend-perceptions-my-diet-ethics [ perceived-ethics ] of consumer-diet [ who ] of friend [ who ] of current-diet
  let friend-perceptions-my-diet-health [ perceived-health ] of consumer-diet [ who ] of friend [ who ] of current-diet
  let my-perceptions-friend-diet-ethics [ perceived-ethics ] of consumer-diet who [ who ] of current-diet-this-friend
  let my-perceptions-friend-diet-health [ perceived-health ] of consumer-diet who [ who ] of current-diet-this-friend
  let l [ link-strength ] of social-contact who [ who ] of friend

  ; Influence from alter to ego regarding alter's diet
  ask my-consumer-diets with [ diet-name = [ name ] of current-diet-this-friend ] [
    ifelse ((l + abs([ ethics-motivation ] of friend - [ ethics-motivation ] of myself)) / 2) > random-float 1 [ ; influence accepted
      set my-perceptions-friend-diet-ethics max list -1 min list 1 my-perceptions-friend-diet-ethics + [ social-information-adherence ] of myself *
      ([ perceived-ethics ] of consumer-diet [ who ] of friend [ who ] of current-diet-this-friend - my-perceptions-friend-diet-ethics)
    ][
      if ((2 - l - abs([ ethics-motivation ] of friend - [ ethics-motivation ] of myself)) / 2) > random-float 1 [ ; influence rejected
        set my-perceptions-friend-diet-ethics max list -1 min list 1 my-perceptions-friend-diet-ethics - [ social-information-adherence ] of myself *
        ([ perceived-ethics ] of consumer-diet [ who ] of friend [ who ] of current-diet-this-friend - my-perceptions-friend-diet-ethics)
      ] ; else influence ignored
    ]

    ifelse ((l + abs([ health-motivation ] of friend - [ health-motivation ] of myself)) / 2) > random-float 1 [
      set my-perceptions-friend-diet-health max list -1 min list 1 my-perceptions-friend-diet-health + [social-information-adherence ] of myself *
      ([ perceived-health ] of consumer-diet [ who ] of friend [ who ] of current-diet-this-friend - my-perceptions-friend-diet-health)
    ][
      if ((2 - l - abs([ health-motivation ] of friend - [ health-motivation ] of myself)) / 2) > random-float 1 [
        set my-perceptions-friend-diet-health max list -1 min list 1 my-perceptions-friend-diet-health - [ social-information-adherence ] of myself *
        ([ perceived-health ] of consumer-diet [ who ] of friend [ who ] of current-diet-this-friend - my-perceptions-friend-diet-health)
      ]
    ]
  ]

  ; Influence from ego to alter regarding ego's diet
  ask [ my-consumer-diets with [ diet-name = [ name ] of current-diet-myself ] ] of friend [
    ifelse ((l + abs([ ethics-motivation ] of myself - [ ethics-motivation ] of friend)) / 2) > random-float 1 [
      set friend-perceptions-my-diet-ethics max list -1 min list 1 friend-perceptions-my-diet-ethics + [ social-information-adherence ] of friend *
      ([ perceived-ethics ] of consumer-diet [ who ] of myself [ who ] of current-diet-this-friend - friend-perceptions-my-diet-ethics)
    ][
      if ((2 - l - abs([ ethics-motivation ] of myself - [ ethics-motivation ] of friend)) / 2) > random-float 1 [
        set friend-perceptions-my-diet-ethics max list -1 min list 1 friend-perceptions-my-diet-ethics - [ social-information-adherence ] of friend *
        ([ perceived-ethics ] of consumer-diet [ who ] of myself [ who ] of current-diet-this-friend - friend-perceptions-my-diet-ethics)
      ]
    ]

    ifelse ((l + abs([ health-motivation ] of myself - [ health-motivation ] of friend)) / 2) > random-float 1 [
      set friend-perceptions-my-diet-health max list -1 min list 1 friend-perceptions-my-diet-health + [ social-information-adherence ] of friend *
      ([ perceived-health ] of consumer-diet [ who ] of myself [ who ] of current-diet-this-friend - friend-perceptions-my-diet-health)
    ][
      if ((2 - l - abs([ health-motivation ] of myself - [ health-motivation ] of friend)) / 2) > random-float 1 [
        set friend-perceptions-my-diet-health max list -1 min list 1 friend-perceptions-my-diet-health - [ social-information-adherence ] of friend *
        ([ perceived-health ] of consumer-diet [ who ] of myself [ who ] of current-diet-this-friend - friend-perceptions-my-diet-health)
      ]
    ]
  ]

  ; Update actual perceptions from temp. variables
  ask my-consumer-diets with [ diet-name = [ name ] of current-diet-this-friend ] [
    set perceived-ethics my-perceptions-friend-diet-ethics
    set perceived-health my-perceptions-friend-diet-health
  ]
  ask [ my-consumer-diets with [ diet-name = [ name ] of current-diet-myself ] ] of friend [
    set perceived-ethics friend-perceptions-my-diet-ethics
    set perceived-health friend-perceptions-my-diet-health
  ]

  ; Update no. of contacts
  ask friend [
    set contacts-this-timestep contacts-this-timestep + 1
  ]
end 

to update-taste-perception
  ask my-consumer-diets [
    ifelse diet-name = ([ [ name ] of current-diet ] of myself) [
      set perceived-taste perceived-taste + ((1 - perceived-taste) / (1 + taste-preference-change-gradient * exp
        ((taste-preference-change-error * random-binomial) + ((- taste-preference-change-rate) * times-consumed))))
    ] [
      set perceived-taste (- 1) + ((perceived-taste + 1) / (1 + (1 / taste-preference-change-gradient) * exp
        (((- taste-preference-change-rate) * (ticks - times-consumed)))))
    ]
  ]
end 

to update-links
  ask social-contacts with [ not is-household-member? and not is-close-friend? ] [
    ifelse interacted-this-timestep? [
      set link-strength link-strength + ((1 - link-strength) / (1 + link-strength-change-gradient * exp
        ((- link-strength-change-rate) * no-of-interactions)))
    ] [
      set link-strength (link-strength / (1 + (1 / link-strength-change-gradient) * exp
        ((- link-strength-change-rate) * (ticks - no-of-interactions))))
    ]
  ]
end 

to update-diet-utility
  let my-current-consumer-diet my-consumer-diets with [ diet-name = [ name ] of [ current-diet ] of myself ]
  let updated-diet-utility-score 0
  ask my-current-consumer-diet [
    set updated-diet-utility-score ifelse-value include-health-motivation? [
      ifelse-value include-ethics-motivation? [
        financially-feasible? * (((
          [ cost-motivation ] of myself * perceived-cost +
          [ taste-motivation ] of myself * perceived-taste +
          [ ethics-motivation ] of myself * perceived-ethics +
          [ health-motivation ] of myself * perceived-health
        ) + 1) / 2)
      ] [
        financially-feasible? * (((
          [ cost-motivation ] of myself * perceived-cost +
          [ taste-motivation ] of myself * perceived-taste +
          [ health-motivation ] of myself * perceived-health
        ) + 1) / 2)
      ]
    ] [
      financially-feasible? * (((
        [ cost-motivation ] of myself * perceived-cost +
        [ taste-motivation ] of myself * perceived-taste
      ) + 1) / 2)
    ]
  ]
  set current-diet-utility-score updated-diet-utility-score
end 

;---------------------------------------------------;
; Setup methods                                     ;
;---------------------------------------------------;

to make-consumers
  create-consumers n-consumers [
    set cost-motivation 0
    set taste-motivation 0
    set ethics-motivation 0
    set health-motivation 0
    set social-information-adherence 0
    set contacts-this-timestep 0
    set household-income 0
    set max-willingness-to-spend max list 0 min list 1 random-normal mean-max-willingness-to-spend sd-max-willingness-to-spend
    set household-size 0
    set satisfaction-threshold max list 0 min list 1 random-normal mean-satisfaction-threshold sd-satisfaction-threshold
    set current-diet-utility-score 0
    set max-diet-utility-score 0
    set min-diet-utility-score 0
    set shape "person"
    set perceptions-set? false
    set household-set? false
  ]

  ;; Set up population demographics (household size, income) from input file
  ; Household size
  file-open household-file
  let headers file-read-line ; Skip header row
  let n-adults 0
  let this-household-size 0
  while [ not file-at-end? ] [
    let household-dat csv:from-row file-read-line ; Format should be frequency, no. of adults, no. of children, weekly income lower bound, weekly income upper bound
    set n-adults item 1 household-dat
    set this-household-size item 1 household-dat + item 2 household-dat
    let this-consumer-set up-to-n-of round ((first household-dat * n-consumers) / n-adults) consumers with [ household-set? = false ]
    ask consumers with [ member? self this-consumer-set ] [
      ; Check that consumer wasn't added to household of another consumer in this-consumer-set
      if household-set? = false [
        set household-size this-household-size ; no. of adults + no. of children
        set household-income random-float item 3 household-dat + random-float (item 4 household-dat - item 3 household-dat)
        ; If there is >1 adult in the household, group adults together and create household-member social ties
        if n-adults > 1 [
          let i 1
          while [ i < n-adults ] [
            let new-hh-member nobody
            ifelse any? other consumers with [ member? self this-consumer-set and not household-set? ] [
              set new-hh-member one-of other consumers with [ member? self this-consumer-set and not household-set? ]
            ] [
              set new-hh-member one-of other consumers with [ household-size = 0 or household-size = 1 ]
            ]
            create-social-contact-with new-hh-member [
              set is-household-member? true
              set is-close-friend? false
              set link-strength max list 0 min list 1 random-normal mean-household-link-strength sd-household-link-strength
              set similarity 1
              set no-of-interactions 0
            ]
            ask new-hh-member [
              set household-income [ household-income ] of myself
              set household-size [ household-size ] of myself
              set household-set? true
            ]
            set i i + 1
          ]
        ]
        set household-set? true
      ]
    ]

    ; If anyone didn't get the right number of adult household members, group them up with one/some of the single people
    if n-adults > 1 and any? consumers with [ household-size = this-household-size and count my-social-contacts with [ is-household-member? ] = 0 ] [
      ask consumers with [ household-size = this-household-size and count my-social-contacts with [ is-household-member? ] = 0 ] [
        let i 1
        let new-hh-member nobody
        while [ i < n-adults ] [
          ifelse any? other consumers with [ household-size = this-household-size and count my-social-contacts with [ is-household-member? ] < n-adults ] [
            set new-hh-member one-of other consumers with [ household-size = this-household-size and count my-social-contacts with [ is-household-member? ] < n-adults ]
          ] [
            set new-hh-member one-of other consumers with [ household-size = 1 ]
          ]
          create-social-contact-with new-hh-member [
            set is-household-member? true
            set is-close-friend? false
            set link-strength max list 0 min list 1 random-normal mean-household-link-strength sd-household-link-strength
            set similarity 1
            set no-of-interactions 0
          ]
          ask new-hh-member [
            set household-size [ household-size ] of myself
            set household-income [ household-income ] of myself
          ]
          set i i + 1
        ]
      ]
    ]
  ]
  file-close

  ; Handle any consumers with missing household size or income data (shouldn't happen) - set as single-person household with random income bracket of single-person households
  ask consumers with [ household-size = 0 or household-income = 0 ] [
    set household-size 1
    set household-income max list 1 random-float max [ household-income ] of consumers with [ household-size = 1 ]
  ]

  ; Arrange consumers in a large cirle, making sure that household members are not next to each other (for setting up non-household social network later)
  layout-circle (sort-by [ [ a b ] -> not member? b [ my-social-contacts ] of a ] consumers ) max-pxcor - 1

  ;; Set up social information adherence
  file-open social-information-adherence-file
  set headers file-read-line ; Skip header row
  let majority 0
  let majority-dat []
  while [ not file-at-end? ] [
    let adherence-dat csv:from-row file-read-line ; Format should be frequency, mean, SD
    if (first adherence-dat > majority) [ set majority-dat adherence-dat]
    ask n-of (first adherence-dat * n-consumers) consumers with [ social-information-adherence = 0 ] [
      set social-information-adherence (random-normal item 1 adherence-dat item 2 adherence-dat) * soc-info-adherence-scaling-factor
    ]
  ]
  file-close
  ; Assign any remaining agents (there should be very few, if any) to majority norm adherence bracket
  ask consumers with [ social-information-adherence = 0 ] [
    set social-information-adherence (random-normal item 1 majority-dat item 2 majority-dat) * soc-info-adherence-scaling-factor
  ]
end 

to make-diets
  create-diets 1 [
    set name "vegan"
;    set color green
    hide-turtle
  ]
  create-diets 1 [
    set name "vegetarian"
;    set color cyan
    hide-turtle
  ]
  create-diets 1 [
    set name "pescatarian"
;    set color blue
    hide-turtle
  ]
  create-diets 1 [
    set name "flexitarian"
;    set color yellow
    hide-turtle
  ]
  create-diets 1 [
    set name "omnivore"
;    set color red
    hide-turtle
  ]
  file-open diet-cost-file
  let headers file-read-line ; Format should be diet name, cost per week
  while [ not file-at-end? ] [
    let diet-cost-data csv:from-row file-read-line
    ask one-of diets with [ name = first diet-cost-data ] [
      set cost item 1 diet-cost-data
    ]
  ]
  file-close
  layout-circle diets (max-pxcor / 2.5)
end 

to make-consumer-diet-links
  let min-diet-cost one-of [ cost ] of diets with-min [ cost ]
  let max-diet-cost one-of [ cost ] of diets with-max [ cost ]
  ask consumers [
   create-consumer-diets-with diets [
      let consumer-end ifelse-value (is-consumer? end1) [ end1 ] [ end2 ]
      let diet-end ifelse-value (is-diet? end2) [ end2 ] [ end1 ]
      let is [ household-income ] of consumer-end * [ max-willingness-to-spend ] of consumer-end
      let h [ household-size ] of consumer-end
      let c [ cost ] of diet-end
      let hc (h * c) - (h * c * ((h - 1) * 0.1)) ; scaled to incorporate discount for households >1
      set diet-name [ name ] of diet-end
      set financially-feasible? ifelse-value hc > is [ 0 ] [ 1 ]
      set perceived-cost ifelse-value (is = 0 or financially-feasible? = 0) [ -1 ] [ -2 * (hc / is) + 1 ]
      hide-link
    ]
  ]

  ; Read in perceptions/motivations data
  file-open motivation-file
  let headers file-read-line ; Skip header row
  let majority 0
  let majority-dat []
  while [ not file-at-end? ] [
    let percep-dat csv:from-row file-read-line ; Format should be frequency, then mean and SD for each cost, taste, ethics, health motivations, respectively, then
    ; mean taste, mean health, mean ethics perceptions for each omnivore, flexitarian, pescetarian, vegetarian, vegan
    if (first percep-dat > majority) [ set majority-dat percep-dat]
    ask n-of (first percep-dat * n-consumers) consumers with [ not perceptions-set? ] [
      set cost-motivation random-normal item 1 percep-dat item 2 percep-dat
      set taste-motivation random-normal item 3 percep-dat item 4 percep-dat
      set ethics-motivation random-normal item 5 percep-dat item 6 percep-dat
      set health-motivation random-normal item 7 percep-dat item 8 percep-dat
      ask my-consumer-diets with [ diet-name = "omnivore" ] [
        set perceived-taste random-normal-in-bounds item 9 percep-dat 0.15 -1 1
        set perceived-health random-normal-in-bounds item 10 percep-dat 0.15 -1 1
        set perceived-ethics random-normal-in-bounds item 11 percep-dat 0.15 -1 1
      ]
      ask my-consumer-diets with [ diet-name = "flexitarian" ] [
        set perceived-taste random-normal-in-bounds item 12 percep-dat 0.15 -1 1
        set perceived-health random-normal-in-bounds item 13 percep-dat 0.15 -1 1
        set perceived-ethics random-normal-in-bounds item 14 percep-dat 0.15 -1 1
      ]
      ask my-consumer-diets with [ diet-name = "pescatarian" ] [
        set perceived-taste random-normal-in-bounds item 15 percep-dat 0.15 -1 1
        set perceived-health random-normal-in-bounds item 16 percep-dat 0.15 -1 1
        set perceived-ethics random-normal-in-bounds item 17 percep-dat 0.15 -1 1
      ]
      ask my-consumer-diets with [ diet-name = "vegetarian" ] [
        set perceived-taste random-normal-in-bounds item 18 percep-dat 0.15 -1 1
        set perceived-health random-normal-in-bounds item 19 percep-dat 0.15 -1 1
        set perceived-ethics random-normal-in-bounds item 20 percep-dat 0.15 -1 1
      ]
      ask my-consumer-diets with [ diet-name = "vegan" ] [
        set perceived-taste random-normal-in-bounds item 21 percep-dat 0.15 -1 1
        set perceived-health random-normal-in-bounds item 22 percep-dat 0.15 -1 1
        set perceived-ethics random-normal-in-bounds item 23 percep-dat 0.15 -1 1
      ]
      set perceptions-set? true
    ]
  ]
  file-close
  ; Assign any remaining agents (there should be very few, if any) to majority motivation bracket
  ask consumers with [ not perceptions-set? ] [
    set cost-motivation random-normal item 1 majority-dat item 2 majority-dat
    set taste-motivation random-normal item 3 majority-dat item 4 majority-dat
    set ethics-motivation random-normal item 5 majority-dat item 6 majority-dat
    set health-motivation random-normal item 7 majority-dat item 8 majority-dat
    ask my-consumer-diets with [ diet-name = "omnivore" ] [
      set perceived-taste random-normal-in-bounds item 9 majority-dat 0.15 -1 1
      set perceived-health random-normal-in-bounds item 10 majority-dat 0.15 -1 1
      set perceived-ethics random-normal-in-bounds item 11 majority-dat 0.15 -1 1
    ]
    ask my-consumer-diets with [ diet-name = "flexitarian" ] [
      set perceived-taste random-normal-in-bounds item 12 majority-dat 0.15 -1 1
      set perceived-health random-normal-in-bounds item 13 majority-dat 0.15 -1 1
      set perceived-ethics random-normal-in-bounds item 14 majority-dat 0.15 -1 1
    ]
    ask my-consumer-diets with [ diet-name = "pescatarian" ] [
      set perceived-taste random-normal-in-bounds item 15 majority-dat 0.15 -1 1
      set perceived-health random-normal-in-bounds item 16 majority-dat 0.15 -1 1
      set perceived-ethics random-normal-in-bounds item 17 majority-dat 0.15 -1 1
    ]
    ask my-consumer-diets with [ diet-name = "vegetarian" ] [
      set perceived-taste random-normal-in-bounds item 18 majority-dat 0.15 -1 1
      set perceived-health random-normal-in-bounds item 19 majority-dat 0.15 -1 1
      set perceived-ethics random-normal-in-bounds item 20 majority-dat 0.15 -1 1
    ]
    ask my-consumer-diets with [ diet-name = "vegan" ] [
      set perceived-taste random-normal-in-bounds item 21 majority-dat 0.15 -1 1
      set perceived-health random-normal-in-bounds item 22 majority-dat 0.15 -1 1
      set perceived-ethics random-normal-in-bounds item 23 majority-dat 0.15 -1 1
    ]
    set perceptions-set? true
  ]


  ; Re-scale consumers' motivations so the total is [0,1]
  ask consumers [
    if not include-health-motivation? [
      set health-motivation 0
    ]
    if not include-ethics-motivation? [
      set ethics-motivation 0
    ]
    let motivation-sum sum (list cost-motivation taste-motivation ethics-motivation health-motivation)
    set cost-motivation cost-motivation / motivation-sum
    set taste-motivation taste-motivation / motivation-sum
    set ethics-motivation ethics-motivation / motivation-sum
    set health-motivation health-motivation / motivation-sum
  ]

  ask consumers [
    ; Choose initial diet using same probability-based multi-criteria method
    choose-diet
  ]
end 

to make-social-network
  ; Iterate over turtles to create initial lattice of specified initial node degree (code adapted from 'Small Worlds' demo model in Models Library, Wilensky 2015)
  let n 0
  while [ n < count consumers ] [
    let i 1
    while [ i <= (initial-avg-degree / 2) ] [
      ask consumer n [
        ; The network layout command in the create-consumers sub should keep household members apart (and these are only social contacts set so far), but check here just to be sure
        if not member? consumer ((n + i) mod count consumers) my-social-contacts [
          create-social-contact-with consumer ((n + i) mod count consumers) [
            set is-household-member? false
            set is-close-friend? false
            set link-strength max list 0 min list 1 random-normal mean-acquaintances-link-strength sd-acquaintances-link-strength
            set similarity consumer-similarity end1 end2
            set no-of-interactions 0
            set interacted-this-timestep? false
          ]
        ]
      ]
      set i i + 1
    ]
    set n n + 1
  ]
  ; Rewire each link according to specified rewiring probability
  rewire-links
end 

;---------------------------------------------------;
; Data collection                                   ;
;---------------------------------------------------;

to record-consumer-data
  let consumer-output-file (word "output/x_" this-seed "_consumerdata.csv")
  if ticks = 0 and file-exists? consumer-output-file [ file-delete consumer-output-file ]
  file-open consumer-output-file
  if ticks = 0 [
    file-print (word "ticks,consumer,current_diet,perceptions_about,financially_feasible,perceived_cost,perceived_taste,perceived_ethics,perceived_health")
  ]
  ask consumers [
    ask my-consumer-diets with [ diet-name = "omnivore" ] [
      file-print (word ticks "," [ who ] of myself "," [ [ name ] of current-diet ] of myself "," diet-name "," financially-feasible? "," perceived-cost "," perceived-taste ","
        perceived-ethics "," perceived-health)
    ]
    ask my-consumer-diets with [ diet-name = "flexitarian" ] [
      file-print (word ticks "," [ who ] of myself "," [ [ name ] of current-diet ] of myself "," diet-name "," financially-feasible? "," perceived-cost "," perceived-taste ","
        perceived-ethics "," perceived-health)
    ]
    ask my-consumer-diets with [ diet-name = "pescatarian" ] [
      file-print (word ticks "," [ who ] of myself "," [ [ name ] of current-diet ] of myself "," diet-name "," financially-feasible? "," perceived-cost "," perceived-taste ","
        perceived-ethics "," perceived-health)
    ]
    ask my-consumer-diets with [ diet-name = "vegetarian" ] [
      file-print (word ticks "," [ who ] of myself "," [ [ name ] of current-diet ] of myself "," diet-name "," financially-feasible? "," perceived-cost "," perceived-taste ","
        perceived-ethics "," perceived-health)
    ]
    ask my-consumer-diets with [ diet-name = "vegan" ] [
      file-print (word ticks "," [ who ] of myself "," [ [ name ] of current-diet ] of myself "," diet-name "," financially-feasible? "," perceived-cost "," perceived-taste ","
        perceived-ethics "," perceived-health)
    ]
  ]
  file-close
end 

to record-consumer-baseline-data
  let consumer-baseline-output-file (word "x_" this-seed "_consumerbaselinedata.csv")
  if ticks = 0 and file-exists? consumer-baseline-output-file [ file-delete consumer-baseline-output-file ]
  file-open consumer-baseline-output-file
  if ticks = 0 [ file-print (word "who,cost_motivation,taste_motivation,ethics_motivation,health_motivation,social_information_adherence,household_income,"
    "max_willingness_to_spend,household_size") ]
  ask consumers [
    file-print (word who "," cost-motivation "," taste-motivation "," ethics-motivation "," health-motivation "," social-information-adherence "," household-income ","
      max-willingness-to-spend "," household-size)
  ]
  file-close
end 

to record-network-data
  let network-output-file (word "x_" this-seed "_networkdata.csv")
  if ticks = 0 and file-exists? network-output-file [ file-delete network-output-file ]
  file-open network-output-file
  if ticks = 0 [ file-print "ticks,end1,end2,is_household_member,is_close_friend,link_strength,similarity,no_of_interactions" ]
  ask social-contacts with [ link-strength > 0 or no-of-interactions > 0 ] [
    file-print (word ticks "," [ who ] of end1 "," [ who ] of end2 "," is-household-member? "," is-close-friend? "," link-strength "," similarity "," no-of-interactions)
  ]
  file-close
end 

to record-run-params
  file-open run-param-output-file
  file-print (word this-seed "," n-consumers "," rewiring-probability "," initial-avg-degree "," mean-close-friends "," max-contacts-per-timestep ","
    link-strength-change-rate "," link-strength-change-gradient "," soc-info-adherence-scaling-factor "," taste-preference-change-rate ","
    taste-preference-change-error "," taste-preference-change-gradient "," mean-max-willingness-to-spend "," sd-max-willingness-to-spend ","
    mean-household-link-strength "," sd-household-link-strength "," mean-friends-link-strength "," sd-friends-link-strength "," mean-acquaintances-link-strength ","
    sd-acquaintances-link-strength "," mean-satisfaction-threshold "," sd-satisfaction-threshold "," social-interaction? "," network-structural-change? ","
    include-health-motivation? "," include-ethics-motivation?)
  file-close
end 

;---------------------------------------------------;
; Helper methods                                    ;
;---------------------------------------------------;
; Report a normally-distributed random float within specified boundaries
; Credit: Seth Tisue https://stackoverflow.com/a/20233830

to-report random-normal-in-bounds [mid dev mmin mmax]
  let result random-normal mid dev
  if result < mmin or result > mmax
    [ report random-normal-in-bounds mid dev mmin mmax ]
  report result
end 

to-report random-binomial
  ifelse random-float 1 < 0.5 [ report -1 ][ report 1 ]
end 

to-report consumer-similarity [a b]
  let hh-size-range max [ household-size ] of consumers - min [ household-size ] of consumers
  if hh-size-range = 0 [ error "Household size range mubst be >0" ]
  let income-range max [ household-income ] of consumers - min [ household-income ] of consumers
  if income-range = 0 [ error "Income range must be >0" ]
  report (1 - abs (([ household-income ] of a - [ household-income ] of b) / income-range)) *
            (1 - abs (([ household-size ] of a - [ household-size ] of b) / hh-size-range))
end 

to rewire-links
  ask consumers [
    ; Don't rewire household links
    ask my-social-contacts with [ not is-household-member? ] [
      if (random-float 1) < rewiring-probability [
        let skip false
        let ego myself
        let alter ifelse-value end1 = ego [ end2 ] [ end1 ]
        ; Identify consumers who are not the first consumer (ego) or ego's current social-contacts (inc. household members) with >0 link strength
        let poss-social-contacts consumers with [ self != ego and ((not social-contact-neighbor? ego or [ link-strength ] of social-contact [ who ] of ego [ who ] of self = 0)) ]
        if (count poss-social-contacts > 0) [
          ; Calculate probability of rewiring to each alter - inverse Euclidean distance of attributes
          let rewire-prob table:make
          ask poss-social-contacts [
            let prob consumer-similarity ego self
            table:put rewire-prob [ who ] of self prob
          ]
          ; Calculate probability of maintaining current social-contact connection
          let prob consumer-similarity alter ego
          table:put rewire-prob [ who ] of alter prob
          ; Choose new friend using 'roulette wheel' selection with probability as weight
          let new-social-contact consumer first (rnd:weighted-one-of-list table:to-list rewire-prob [ [ a ] -> last a ])
          ; If the new social-contact is the same as the old one, skip creating a new link (and don't kill the previous link)
          ifelse new-social-contact = alter [
            set skip true
          ] [
            ; Create new social contact
            ask ego [
              create-social-contact-with new-social-contact [
                set is-household-member? false
                set is-close-friend? false
                set link-strength max list 0 min list 1 random-normal mean-acquaintances-link-strength sd-acquaintances-link-strength
                set similarity consumer-similarity end1 end2
                set no-of-interactions 0
                set interacted-this-timestep? false
              ]
            ]
          ]
          if skip = false [
            ; Remove old link if a new one was created
            set link-strength 0
            set is-close-friend? false
            set is-household-member? false
            hide-link
          ]
        ]
      ]
    ]

  ]
  ; Create "close ties" social links
  ask social-contacts with [ not is-household-member? ] [
    if (mean-close-friends / initial-avg-degree) > random-float 1 [
      set is-close-friend? true
      set link-strength max list 0 min list 1 random-normal mean-friends-link-strength sd-friends-link-strength
    ]
  ]
  ; Create fully-connected network to store similarity and social distance, but set all other network links to link-strength = 0
  ask consumers [
    let ego self
    create-social-contacts-with other consumers with [ not member? self my-social-contacts ] [
      let alter ifelse-value end1 = ego [ end2 ] [ end1 ]
      set link-strength 0
      set is-close-friend? false
      set is-household-member? false
      set similarity consumer-similarity end1 end2
      set no-of-interactions 0
      hide-link
    ]
  ]
end 

to-report avg-node-degree
  let all-node-degrees []
  ask consumers [ set all-node-degrees lput count my-social-contacts with [ not is-household-member? and link-strength > 0 ] all-node-degrees ]
  report mean all-node-degrees
end 

There are 4 versions of this model.

Uploaded by When Description Download
Natalie Davis almost 2 years ago Bug fixes Download this version
Natalie Davis almost 2 years ago Revised version of model Download this version
Natalie Davis over 2 years ago Added GPL3 license Download this version
Natalie Davis over 2 years ago Initial upload Download this version

Attached files

File Type Description Last updated
final_prices_input_weekly.csv data Dummy prices file almost 2 years ago, by Natalie Davis Download
household_input.csv data Household size and income data almost 2 years ago, by Natalie Davis Download
motivations_perceptions_input.csv data Motivations and perceptions data almost 2 years ago, by Natalie Davis Download
norm_adherence_parameterization.csv data Social information adherence data almost 2 years ago, by Natalie Davis Download

This model does not have any ancestors.

This model does not have any descendants.