3

Following up on this question about how to find the Markov steady state, I'm now running into the problem that it works perfectly on my lab computer, but it doesn't work on any other computer. Specifically, it always finds the correct number of near-one eigenvalues, and thus which nodes are attractor nodes, but it doesn't consistently find all of them and they aren't grouped properly. For example, using the 64x64 transition matrix below, the computers in which it doesn't work it always produces one of three different wrong collections of attractors at random. On the smaller matrix M1 below, all computers tested get the same, correct result to attractor groups and stationary distribution.

All the machines tested are running Win7x64 and WinPython-64bit-2.7.9.4. One computer always gets it right, three others always get it wrong in the same ways. Based on several posts I found like this and this, this sounds like it might be caused by differences in the floating point accuracy of the calculations. Unfortunately I don't know how to fix this; I mean, I don't know how to alter the code for pulling the left eigenvalues from the matrix in order to force a specific accuracy that all computer can handle (and I don't think it has to be very accurate for this purpose).

That's just my current best guess for how the results could differ. If you have a better idea of why this is happening and how to stop it form happening then that's great too.

If there is a way to make scipy consistent from run-to-run and computer to computer, I don't think it would depend on the details of my method, but because it was requested, here it is. Both of the matrices have 3 attractors. In M1 the first one [1,2] is an orbit of two states, the other two [7] and [8] are equilibria. M2 is a 64x64 transition matrix with equilibria at [2] and [26] as well as an orbit using [7,8].

But instead of finding that set of attractors, it sometimes reports [[26],[2],[26]] and sometimes [[2,7,8,26],[2],[26]] and sometimes ... it's not getting the same answer each run, and it's never getting [[2],[7,8],[26]] (in any order).

import numpy as np
import scipy.linalg

M1 = np.array([[0.2, 0.8, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
              [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
              [0.6, 0.4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
              [0.0, 0.0, 0.2, 0.0, 0.1, 0.1, 0.3, 0.3],
              [0.0, 0.0, 0.2, 0.2, 0.2, 0.2, 0.1, 0.1],
              [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.5],
              [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0],
              [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]])

M2 = np.genfromtxt('transitionMatrix1.csv', delimiter=',')

# For easy switching
M = M2
# Confirm the matrix is a valid Markov transition matrix
#print np.sum(M,axis=1)

The rest is the same code from the previous question, included here for your convenience.

#create a list of the left eigenvalues and a separate array of the left eigenvectors
theEigenvalues, leftEigenvectors = scipy.linalg.eig(M, right=False, left=True)  
# for stationary distribution the eigenvalues and vectors are always real, and this speeds it up a bit
theEigenvalues = theEigenvalues.real                 
#print theEigenvalues 
leftEigenvectors = leftEigenvectors.real
#print leftEigenvectors 
# set how close to zero is acceptable as being zero...1e-15 was too low to find one of the actual eigenvalues
tolerance = 1e-10
# create a filter to collect the eigenvalues that are near enough to zero                                    
mask = abs(theEigenvalues - 1) < tolerance
# apply that filter           
theEigenvalues = theEigenvalues[mask]
# filter out the eigenvectors with non-zero eigenvalues                
leftEigenvectors = leftEigenvectors[:, mask]         
# convert all the tiny and negative values to zero to isolate the actual stationary distributions
leftEigenvectors[leftEigenvectors < tolerance] = 0   
# normalize each distribution by the sum of the eigenvector columns
attractorDistributions = leftEigenvectors / leftEigenvectors.sum(axis=0, keepdims=True)   
# this checks that the vectors are actually the left eigenvectors
attractorDistributions = np.dot(attractorDistributions.T, M).T      
# convert the column vectors into row vectors (lists) for each attractor     
attractorDistributions = attractorDistributions.T                        
print attractorDistributions
# a list of the states in any attractor with the stationary distribution within THAT attractor
#theSteadyStates = np.sum(attractorDistributions, axis=1)                
#print theSteadyStates 
Community
  • 1
  • 1
Aaron Bramson
  • 1,176
  • 3
  • 20
  • 34
  • 2
    I left you a message on the answer you put on your other question, but do you have a complete repro case you could add? Subtle numerical problems can be very hard to analyse at the best of times, but without code it is going to be impossible to provide any sort of useful answer. – talonmies Jan 12 '16 at 13:44
  • I'm with @talonmies: Please provide a [MCVE]! – jkalden Jan 12 '16 at 14:10
  • 1
    Quick guess: The eigenvalues and eigenvalues _are not returned sorted_. The order they're returned in will vary much more than the values themselves. Is it possible you're assuming that indexing the first/last eigenvalue/vector is always the largest or smallest? – Joe Kington Jan 12 '16 at 14:25
  • The code is from the previous question, but that's not complete or verifiable without a transition matrix to apply it to. The matrix I'm working with is 64x64, so I'll come up with a smaller (minimal) one that recreates the problem and post it. @JoeKington: Good guess, but the order doesn't (shouldn't) matter for my code; it returns only, but not all, steady states and varies in which it finds from run to run. – Aaron Bramson Jan 12 '16 at 15:01
  • 1
    When you say "eigenvectors are wrong", have you checked that they are actually not eigenvectors? I.e. does not `A x` equal `lambda x` to within some accuracy? – ev-br Jan 12 '16 at 18:15
  • @ev-br They ARE eigenvectors for the eigenvalues reported on each computer, but it's a different set run-by-run and on different computers. I'm not really asking for help on the code for finding eigenvalues or eigenvectors, that already works perfectly (on one computer), the root of the question is how to force scipy to produce THE SAME output across computers and run-by-run, whatever the outcome is, then I can actually address whether it's correct or not. It's possible that the eigenvalues are wrong too, but that's just a symptom of the real problem. – Aaron Bramson Jan 13 '16 at 06:04
  • 1
    All caps are not helpful, you know :-). What I'm asking is whether you checked that you're not getting different linear combinations of eigenvectors for degenerate eigenvalues. – ev-br Jan 13 '16 at 13:04
  • @ev-br Yes, I checked that, but that's not the problem here. The problem seems to be that the algorithm that scipy uses to calculate eigenvalues/vectors takes a random initial vector, and the choice of initial vector has an effect on the relative values in the eigenvectors so that positions corresponding to different eigenvalues also have large, and sometimes larger, values than the actual eigenvalue it corresponds to. I want to set this random seed so all computers produce the same output, then I'll make sure it's the correct output (that one computer already gets). – Aaron Bramson Jan 13 '16 at 15:20
  • scipy.eig uses lapack `dgeev` to compute the eigenvalues. How it works and the error bounds for this approach (it's a Schur factorisation method), are [documented](http://www.netlib.org/lapack/lug/node93.html) – talonmies Jan 13 '16 at 16:34

1 Answers1

-3

The unfortunate answer is that there is no way to fix the seed for scipy, and therefore no way to force it to output consistent values. This also means that there is no way for it to reliably produce correct answers because only one answer is correct. My attempts to get a definitive answer or fix from the scipy people were completely dismissed, but somebody may find some wisdom in those words when facing this issue.

As a concrete example of the problem, when you run the code above you may sometimes get the following set of eigenvectors supposedly representing the steady states of each of the attractors in the system. My home computer always produces this result (which is different form my laptop and lab computer). As stated in the question, the correct attractors are [[2],[7,8],[26]]. The equilibria of [2] and [6] are correctly identified, but the distribution for [7,8] instead returns a non-valid probability distribution over [2,26]. The correct answer is [0.19835, 0.80164] over [7,8] respectively. My lab computer correctly finds that solution, but so far six other computers have failed to do so.

What this means is that (unless there is some other unidentified error in my code) scipy.linalg is worthless for finding steady states of Markov models. Even though it works some of the time, it cannot be relied upon to provide the correct answer, and therefore should be avoided completely...at least for Markov model steady states, and probably for everything to do with eigenvectors. It just doesn't work.

I will post code on how to reliably generate the stationary distribution of a Markov model without using scipy if anybody asks a question about it. It runs a bit slower, but it's always the same and always correct.

[[ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.25707958  1.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.06867772  0.          1.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
 [ 0.          0.          0.        ]
...
 [ 0.          0.          0.        ]]
Aaron Bramson
  • 1,176
  • 3
  • 20
  • 34
  • 1
    If you are going to downvote this answer, please comment with an improvement or suggest a better answer. It's not particularly helpful to only express that you don't like it. – Aaron Bramson Feb 22 '16 at 04:01