{"record":{"id":"05ec572dbc3d7ab2","repo":"aosabook/500lines","slug":"event-probabilities-do-not-sum-to-1","errorCode":null,"errorMessage":"event probabilities do not sum to 1","messagePattern":"event probabilities do not sum to 1","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"sampler/sampler.markdown","lineNumber":223,"sourceCode":"\n    def __init__(self, p, rso=np.random):\n        \"\"\"Initialize the multinomial random variable.\n\n        Parameters\n        ----------\n        p: numpy array of length `k`\n            The event probabilities\n        rso: numpy RandomState object (default: None)\n            The random number generator\n\n        \"\"\"\n\n        # Check that the probabilities sum to 1. If they don't, then\n        # something is wrong! We use `np.isclose` rather than checking\n        # for exact equality because in many cases, we won't have\n        # exact equality due to floating-point error.\n        if not np.isclose(np.sum(p), 1.0):\n            raise ValueError(\"event probabilities do not sum to 1\")\n\n        # Store the parameters that were passed in\n        self.p = p\n        self.rso = rso\n\n        # Precompute log probabilities, for use by the log-PMF, for\n        # each element of `self.p` (the function `np.log` operates\n        # elementwise over NumPy arrays, as well as on scalars.)\n        self.logp = np.log(self.p)\n```\n\nThe class takes as arguments the event probabilities, $p$, and a\nvariable called `rso`. First, the constructor checks that the\nparameters are valid; i.e., that `p` sums to 1. Then it stores\nthe arguments that were passed in, and uses the event probabilities to\ncompute the event *log* probabilities. (We'll go into why this is\nnecessary in a bit). The `rso` object is what we'll use later to\nproduce random numbers. (We'll talk more about what it is a bit later","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/aosabook/500lines/blob/fba689d101eb5600f5c8f4d7fd79912498e950e2/sampler/sampler.markdown#L205-L241","documentation":"Raised by the constructor of the multinomial distribution class in the sampler chapter. It validates that the supplied event-probability vector p sums to 1 using np.isclose(np.sum(p), 1.0) to tolerate floating-point drift; if not close it raises ValueError('event probabilities do not sum to 1'). The probabilities are then stored and used to precompute log-probabilities for the log-PMF.","triggerScenarios":"Constructing the distribution with a p whose sum is not ~1.0, e.g. [0.3, 0.3] (sum 0.6), [0.5, 0.6] (sum 1.1), or an empty array. Fires at instantiation time, before any sampling.","commonSituations":"Reading probabilities from a config/CSV that were not normalised; hand-tuning weights and forgetting to renormalise; rounding/truncation that drifts the sum outside np.isclose tolerance (~1e-8 relative); passing log-probabilities or counts instead of probabilities.","solutions":["Normalise the vector before constructing: p = np.asarray(p, float); p = p / p.sum().","Fix the source data so the intended probabilities genuinely sum to 1.","If the values are weights, divide by their sum explicitly and document the transformation.","Verify the array is non-empty and 1-D of length k."],"exampleFix":"# before\np = np.array([0.2, 0.3, 0.3])            # sum 0.8 -> ValueError\ndist = MultinomialDistribution(p, rso)\n\n# after: normalise first\np = np.array([0.2, 0.3, 0.3], dtype=float)\np = p / p.sum()                          # -> [0.25, 0.375, 0.375], sum 1.0\nassert np.isclose(p.sum(), 1.0)\ndist = MultinomialDistribution(p, rso)","handlingStrategy":"validation","validationCode":"import numpy as np\np = np.asarray(raw_p, dtype=float)\nassert p.ndim == 1 and p.size > 0, 'p must be a non-empty 1-D array'\nif not np.isclose(p.sum(), 1.0):\n    p = p / p.sum()      # normalise in place\nassert np.isclose(p.sum(), 1.0)\ndist = MultinomialDistribution(p, rso=rso)","typeGuard":"def is_probability_vector(p):\n    p = np.asarray(p, dtype=float)\n    return p.ndim == 1 and p.size > 0 and np.all(p >= 0) and np.isclose(p.sum(), 1.0)","tryCatchPattern":"try:\n    dist = MultinomialDistribution(p, rso=rso)\nexcept ValueError as e:\n    if 'sum to 1' in str(e):\n        p = np.asarray(p, float) / np.sum(p)\n        dist = MultinomialDistribution(p, rso=rso)\n    else:\n        raise","preventionTips":["Normalise at the data-loading boundary, not at every construction.","Reject negative entries alongside the sum check.","Pin numpy version so np.isclose tolerance is stable across envs.","Add a unit test that feeds [0.2,0.3,0.3] and expects success after normalisation."],"tags":[],"backgroundTag":null,"analyzedSha":"fba689d101eb5600f5c8f4d7fd79912498e950e2","analyzedAt":"2026-08-13T06:26:32.792Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}