Edit page

If the literal meaning of an explanation “B because A” is “A, B, and (counterfactually) if not A then not B,” the following model would be the literal interpretation.

We use the counterfactual model from Lucas & Kemp (2015), with our specific implementation details at forestdb.org/models/lucas-kemp-2015-replication.html.

If we use this literal interpretation, then a listener who hears “The neighbors are angry because I cooked bacon,” will be slightly more likely to believe that the smoke alarm went off than if they heard the sentence, “The neighbors are angry and I cooked bacon.” But the difference is small, since the probability of smoke alarm given bacon and neighbors is already basically at ceiling.

This model yields the following underwhelming result: In the bacon example discussed in the counterfactuals page, the literal interpretation model thinks it’s a tiny bit less likely that the smoke alarm went off when the utterance is “The neighbors are angry because I cooked bacon” (which implies the two events are causally connected by the smoke alarm) than when the utterance is “The neighbors are angry and also I cooked bacon.”

//parameters
var impliesComponents = true; //if true, A and B are part of the meaning of "B because A"
var stickiness = 0.53;
// overall utilities
///fold:
var world2String = function(world) {
  map(function(x) {
    return x[1] ? x[0] : '-';
  }, _.pairs(world)).join('');
};
var strPrint = function(erp) {
  print(Enumerate(function() {
    return world2String(sample(erp));
  }));
  return true;
};
var mapObject = function(fn, obj) {
  return _.object(
    map(
      function(kv) {
        return [kv[0], fn(kv[0], kv[1])]
      },
      _.pairs(obj))
  );
};

// deals with erps that have no support
// (e.g. because conditions on something impossible)
var checkPossible = function(unfactoredERP) {
  var factorERP = Enumerate(function() {
    return sample(unfactoredERP).factor;
  });
  if (factorERP.score([], -Infinity) == 0) {
    // a sort of way of throwing errors if we try to evaluate an impossible utterance
    return 'impossible';
  } else {
    return Enumerate(function() {
      var result = sample(unfactoredERP);
      factor(result.factor);
      return result.result;
    });
  }
};

// enumerate with an extra "check" step
var checkEnumerate = function(thunk) {
  return checkPossible(Enumerate(thunk));
};

// get probability of erp result "true"
var getP = function(erp, value) {
  var value = value ? value : true;
  return Math.exp(erp.score([], value));
};

var containsBecause = function(utt) {
  var words = utt.split(' ');
  return words.length > 1 ? words[1]=='because' : false;
};

var parseBecauseStatement = function(utt) {
  var words = utt.split(' ');
  if (words.length != 3) {
    print('warning 98: expected because statement, got: ' + utt);
  };
  return words;
};

var getConsequent = function(utt) {
  var words = parseBecauseStatement(utt);
  return words[0]; // **consequent** because antecedent
};

var getAntecedent = function(utt) {
  var words = parseBecauseStatement(utt);
  return words[2]; // consequent because **antecedent**
};
///
//stories to choose from
///fold:
var stories = {
  bacon: {
    rand : function() {
      return {
        uB: flip(0.9),
        uS: flip(0.9),
        uN: flip(0.1)
      };
    },
    vars: function(rVs) {
      var B = rVs.uB;
      var S = B & rVs.uS ? true : false;
      var N = S | rVs.uN ? true : false;
      return {
        B: B,
        S: S,
        N: N
      };
    }
  },
  story1: {
    rand : function() {
      return {
        uA: flip(0.1),
        uB: flip(0.1)
      };
    },
    vars: function(rVs) {
      var A = rVs.uA;
      var B = rVs.uB;
      return {
        A: A,
        B: B
      };
    }
  },
  story2: {
    rand : function() {
      return {
        uA: flip(0.1),
        uAB: flip(0.75),
        uBC: flip(0.75)
      };
    },
    vars: function(rVs) {
      var A = rVs.uA;
      var B = A & rVs.uAB ? true : false;
      var C = B & rVs.uBC ? true : false;
      return {
        A: A,
        B: B,
        C: C
      };
    }
  },
  story3: {
    rand : function() {
      return {
        uA: flip(0.25),
        uB: flip(0.1)
      };
    },
    vars: function(rVs) {
      var A = rVs.uA;
      var B = rVs.uB;
      var C = (A & !B) | (!A & B) ? true : false;
      return {
        A: A,
        B: B,
        C: C
      };
    }
  },
  story4: {
    rand : function() {
      return {
        uA: flip(0.9),
        uB: flip(0.9)
      };
    },
    vars: function(rVs) {
      var A = rVs.uA;
      var B = rVs.uB;
      var C = (A | B) ? true : false;
      var D = C;
      return {
        A: A,
        B: B,
        C: C,
        D: D
      };
    }
  },
  story5: {
    rand : function() {
      return {
        uA: flip(0.75),
        uB: flip(0.75),
        uBC: flip(0.9)
      };
    },
    vars: function(rVs) {
      var A = rVs.uA;
      var B = rVs.uB;
      var C = A ? true : ((B & rVs.uBC) ? true : false);
      var D = C;
      return {
        A: A,
        B: B,
        C: C,
        D: D
      };
    }
  },
  story6: {
    rand : function() {
      return {
        uA: flip(0.25),
        uAB: flip(0.1),
        uBC: flip(0.1)
      };
    },
    vars: function(rVs) {
      var A = rVs.uA;
      var B = (A & !rVs.uAB) | (!A & rVs.uAB) ? true : false;
      var C = (B & !rVs.uBC) | (!B & rVs.uBC) ? true : false;
      return {
        A: A,
        B: B,
        C: C
      };
    }
  },
  tugOfWar: {
    rand: function() {
      return {
        uAS: flip(0.5),
        uAL: flip(0.3),
        uBS: flip(0.5),
        uBL: flip(0.3),
        uAW: flip(0.5)
      };
    },
    vars: function(rVs) {
      // strong is strength = 2; weak is strength = 1
      var aliceStrong = rVs.uAS;
      var aliceStrength = aliceStrong ? 2 : 1;
      var aliceLazy = rVs.uAL;
      var bobStrong = rVs.uBS;
      var bobStrength = bobStrong ? 2 : 1;
      var bobLazy = rVs.uBL;
      // laziness decreases strength by half
      var alicePulling = aliceLazy ? aliceStrength/2 : aliceStrength;
      var bobPulling = bobLazy ? bobStrength/2 : bobStrength;
      var aliceWin = (alicePulling>bobPulling)?true:(bobPulling>alicePulling)?false:rVs.uAW;
      
      return {
        aliceStrong: aliceStrong,
        aliceLazy: aliceLazy,
        bobStrong: bobStrong,
        bobLazy: bobLazy,
        aliceWin: aliceWin
      };
    }
  }
};
///
var rand = stories.bacon.rand;
var vars = stories.bacon.vars;
// CF model:
///fold:

// for counterfactual simulation,
// sometimes (with probability "stickiness") sample the actual
// random statue variable. othertimes, sample a new one from the prior
var mapObj = function(fn, obj) {
  return _.object(map(function(kv) { return [kv[0], fn(kv[1], kv[0])]; },
                      _.pairs(obj)));
};

var stickyRand = function(actualRVs) {
  var freshRVs = rand();
  return mapObject(function(key, val) {
    return flip(stickiness) ? actualRVs[key] : freshRVs[key];
  }, actualRVs);
};

// get value of propsition in a world
var contextualEval = cache(function(proposition, world) {
  var context = reduce(
    function(keyVal, acc) {
      return acc + ' var ' + keyVal[0] + ' = ' + JSON.stringify(keyVal[1]) + ';';
    },
    "",
    _.pairs(world)
  );
  return webpplEval(context + proposition + ';');
});

// counterfactual ERP, conditioning on actual world
var cfERP = function(cfParams) {
  return checkEnumerate(function() {
    //checks to handle different usage
    // if no cfAntecedent, condition cfERP on true
    // if no cfConsequent, return world
    // if no actualRVs, use actualWorld
    // if no actualWorld and no actualRVs....sample a completely random world

    if (cfParams.actualRVs & cfParams.actualWorld) {
      print([
        'warning 1234:',
        '*both* RVs and world were given as input!',// for pretty printing
        'actualRVs=',
        JSON.stringify(cfParams.actualRVs),
        '; actualWorld=',
        JSON.stringify(cfParams.actualWorld)
      ].join(' '));
    };

    // if we know the actualRVs, use 'em
    var actualRVs = cfParams.actualRVs ? cfParams.actualRVs : rand();
    var actualWorld = vars(actualRVs);

    // if we know *some* stuff about the actualWorld, condition on that
    var cond3 = (cfParams.actually ?
                 contextualEval(cfParams.actually, actualWorld) :
                 true);

    // if we know the actualWorld, condition on that
    var cond1 = (cfParams.actualWorld ?
                 _.isEqual(cfParams.actualWorld, actualWorld) :
                 true);

    var cfRVs = stickyRand(actualRVs);
    var cfWorld = vars(cfRVs);

    // if we are given a counterfactual antecedent, condition on that
    var cond2 = (cfParams.cfAntecedent ?
                 contextualEval(cfParams.cfAntecedent, cfWorld) :
                 true);

    // if cfConsequent specified, return it, else return whole cfWorld
    var result = (cfParams.cfConsequent ?
                  contextualEval(cfParams.cfConsequent, cfWorld) :
                  cfWorld);

    return {
      result: result,
      factor: cond1 & cond2 & cond3 ? 0 : -Infinity
    }
  });
};
///

var becauseERP = function(bcParams) {
  // "B because A"
  var antecedentA = bcParams.antecedentA ? bcParams.antecedentA : 'true';
  var consequentB = bcParams.consequentB;

  // convert to negation for cf ("if !A then !B")
  var ifNotA = "!(" + antecedentA + ")";
  var thenNotB = "!(" + consequentB + ")";

  // actualRVs may or may not be defined. we pass them into the cfERP
  var actualRVs = bcParams.actualRVs;

  // actualWorld may or may not be defined. we pass it into cfERP
  var actualWorld = bcParams.actualWorld;

  return cfERP({
    // "A because B" means if not cfA then not cfB
    cfAntecedent: ifNotA,
    cfConsequent: thenNotB,
    actualRVs: actualRVs,
    actualWorld: actualWorld,
    // "A because B" (maybe) means that A and B are actually true.
    actually: impliesComponents ? antecedentA + ' & ' + consequentB : 'true'
  });
};

var meaningScore = cache(function(meaningParams) {
  var utt = meaningParams.utt;
  var actualWorld = meaningParams.actualWorld;
  var actualRVs = meaningParams.actualRVs;
  if (meaningParams.actualRVs & meaningParams.actualWorld) {
    print([
      'warning 435:',
      '*both* RVs and world were given as input!',// for pretty printing
      'actualRVs=',
      JSON.stringify(actualRVs),
      '; actualWorld=',
      JSON.stringify(actualWorld)
    ].join(' '));
  };

  if (utt == 'nothing') {
    return 0; // saying nothing is always "true"
  } else if (containsBecause(utt)) {
    var antecedentA = getAntecedent(utt);
    var consequentB = getConsequent(utt);
    var resultERP = becauseERP({
      antecedentA: antecedentA,
      consequentB: consequentB,
      actualWorld: actualWorld
    });
    return (resultERP=='impossible' ?
            -Infinity :
            // score for !cfConsequent given !cfAntecedent
            // and actually antecedent and consequent
            resultERP.score([], true));
  } else {
    return contextualEval(utt, actualWorld) ? 0 : -Infinity;
  }
});

var literalERP = cache(function(utt) {
  return checkEnumerate(function() {
    var actualRVs = rand();
    var actualWorld = vars(actualRVs);
    var factor1 = meaningScore(
      {utt: utt, actualWorld: actualWorld}
    );
    return {
      result: actualWorld,
      factor: factor1
    }
  });
});

var probabilityOfSmokeAlarm = function(utt) {
  var interpretation = Enumerate(function() {
    return sample(literalERP(utt)).S;
  });
  return getP(interpretation);
};

print(probabilityOfSmokeAlarm('N because B'));
print(probabilityOfSmokeAlarm('N & B'));