php/Math   
Recreational Mathematics   
   home  |  library  |  contact
 Math Notes
 Math Programming [25]
 Regression [3]
 Data Mining [17]
 Notation [6]
 Linear Algebra [9]
 Stats & Prob [15]
 Math Cognition [5]
 Space & Physics [6]
 Formulas [5]
 Fun & Games [2]
 Haskell [1]
 Bayes Theory [1]
 Site News [0]
 Math Projects [5]
 Polynomials [1]
 Calculus [9]
 Number Theory [3]
 Optimization [2]
 Financial [1]

 Math Links
 PHP/ir
 Andrew Gelman
 Chance Wiki
 Daniel Lemire
 KD Knuggets
 Social Stats
 MySQL Performance
 Hunch.net
 Matthew Hurst
 JMLR
 JSS
 Hal Daume III
 Math Notes >> Permanent Link

Matrix manipulation functions for PHP: Part 2 [Regression
Posted on January 7, 2012 @ 01:41:26 PM by Paul Meagher

In the last post, I bemoaned the fact that PHP lacks some useful matrix manipulation functions that matlab/octave support. I decided to see how difficult it would be to create a script that would 1) parse a matlab/octave-like recipe for creating a matrix, and 2) generate a matrix based upon that parse. I initially thought such work would require some tricky regular expressions for parsing the recipe, but it appears that the language for specifying matrices was designed to make parsing relatively easy and that major parts of the parsing could be handled by exploding on various control characters. I'm not claiming that the code below implements all of the matlab/octave language for specifying matrices, however, it does support much of functionality you will see discussed in matlab/octave books.

<?php
/**
* @script matrix.php
*
* Script for creating matrices using matlab/octave like recipes.
*
* @author Paul Meagher
* @version 0.1
* @modified Jan 6, 2012
*/


/**
* Returns a parse of the recipe for a desired matrix.
* The parse that is returned contains a "valid" flag 
* that is set to false (i.e., 0) if the number of 
* columns in each row is unequal.
*/
function parse_matrix_recipe($recipe) {
  
  
// Initialize parse validity status to 1. Parse validity 
  // status will be set to 0 if expression is invalid.    
  
$parse['valid'] = 1;       
  
  
$num_rows 0;
  
$num_cols 0;

  
// Get rid of excess whitespace.
  
$recipe preg_replace('/\s\s+/'' '$recipe);
      
  
// Get all the rows.
  
$rows explode(';'$recipe);
  
  
$num_rows count($rows);
  
  
// Parse all the rows  
  
for($i=0$i<$num_rows$i++) {
  
    
// Check first if the row has the : range delimiter.
    
if (substr_count($rows[$i], ":")) {
  
      list(
$start$limit) = explode(":"$rows[$i]);
        
      if (
settype($start"int") AND settype($limit"int")) {
    
        
$parse[$i]['start'] = $start;
        
$parse[$i]['limit'] = $limit;        
              
        
// Counting number of elements generated by range function
        // is an inefficient way of checking that all rows have
        // same number of columns, but it work.  Optimize later :-)
        
if ($i == 0
          
$init_num_cols $row_num_cols count(range($start$limit));
        else
          
$row_num_cols count(range($start,$limit));
                        
      } else {        
        
        
// Cannot convert range values to integers.
        
$parse['valid'] = 0;
        break;
        
      }
    
    } else {          
            
      
$parse[$i]['array'] = explode(" "$rows[$i]);
            
      if (
$i == 0
        
$init_num_cols $row_num_cols count($parse[$i]['array']);
      else
        
$row_num_cols count($parse[$i]['array']);
            
    }

    
// Exit loop and set parse to false if one row has more columns 
    // than another.
    
if ($init_num_cols != $row_num_cols) {
      
$parse['valid'] = 0;       
      break;
    }    
    
  }
  
  
// If parse is valid, set matrix dimensions.  
  
if ($parse['valid'] == 1) {
    
$parse['num_rows'] = $num_rows;
    
$parse['num_cols'] = $init_num_cols;  
  }
  
  return 
$parse;

}

/**
* Uses parse elements to generate and return a matrix
*/
function generate_matrix($parse) {
 
  
$mat = array();
 
  
$num_rows $parse['num_rows'];
  
  if (
$num_rows == 1) {    

    
// Assign supplied array to vector.
    
if (isset($parse[0]['array']))
      
$vec $parse[0]['array'];
    
// Assign specified range of values to vector.  
    
else 
      
$vec range($parse[0]['start'], $parse[0]['limit']);
      
    return 
$vec;
    
  } else {    
    
    for(
$i=0$i<$num_rows$i++) {        

      
// Assign supplied array to row. 
      
if (isset($parse[$i]['array']))      
        
$mat[$i] = $parse[$i]['array'];
      
// Assign range of values to row.  
      
else
        
$mat[$i] = range($parse[$i]['start'], $parse[$i]['limit']);
    }
    
    return 
$mat;
    
  }

}

/**
* Main function for creating matrices.
*/
function m($recipe) {  
  
  
$parse parse_matrix_recipe($recipe);
  
  if (
$parse['valid'] == 1
    return 
generate_matrix($parse); 
  else
    return array();
    
}

?>

As you can see above, the main function used to specify a matrix is the m( ) function. I envision a complementary submatrix operator, sm( ), that would be used to access a submatrix of a supplied matrix (i.e., sm($recipe, $A)). Matlab/octaves language for specifying a submatrix is more complex than the language for specifying a matrix, so coding the sm( ) function will be a bit more challenging. You don't really know how challenging, however, until you try ...

Below is a test script that shows that the matrix.php script outputs the appropriate matrices given matlab/octave like recipes.

<?php

include "matrix.php";

$recipe1 '-1:1; 3:1';
$recipe2 '1;2;3;4';
$recipe3 '1 2  3;4 5   6';
$recipe4 '1';
$recipe5 '1 2 3;4:6';

$A m($recipe1);
$B m($recipe2);
$C m($recipe3);
$D m($recipe4);
$E m($recipe5);

echo 
"<pre>";
echo 
"\$A = m('".$recipe1."') \n";
echo 
"\$A = \n";
print_r($A);
echo 
"\n";

echo 
"\$B = m('".$recipe2."') \n";
echo 
"\$B = \n";
print_r($B);
echo 
"\n";

echo 
"\$C = m('".$recipe3."') \n";
echo 
"\$C = \n";
print_r($C);
echo 
"\n";

echo 
"\$D = m('".$recipe4."') \n";
echo 
"\$D = \n";
print_r($D);
echo 
"\n";

echo 
"\$E = m('".$recipe5."') \n";
echo 
"\$E = \n";
print_r($E);
echo 
"</pre>";

?>

Here is what the output of this script looks like:

$A = m('-1:1; 3:1') 
$A = 
Array
(
    [0] => Array
        (
            [0] => -1
            [1] => 0
            [2] => 1
        )
    [1] => Array
        (
            [0] => 3
            [1] => 2
            [2] => 1
        )
)

$B = m('1;2;3;4') 
$B = 
Array
(
    [0] => Array
        (
            [0] => 1
        )

    [1] => Array
        (
            [0] => 2
        )

    [2] => Array
        (
            [0] => 3
        )

    [3] => Array
        (
            [0] => 4
        )
)

$C = m('1 2  3;4 5   6') 
$C = 
Array
(
    [0] => Array
        (
            [0] => 1
            [1] => 2
            [2] => 3
        )
    [1] => Array
        (
            [0] => 4
            [1] => 5
            [2] => 6
        )
)

$D = m('1') 
$D = 
Array
(
    [0] => 1
)

$E = m('1 2 3;4:6') 
$E = 
Array
(
    [0] => Array
        (
            [0] => 1
            [1] => 2
            [2] => 3
        )
    [1] => Array
        (
            [0] => 4
            [1] => 5
            [2] => 6
        )
)

Originally I suggested that we use a mat( ) function to both create and access a matrix. I decided that if we are going to shorten "matrix" to "mat" we might as well go all the way and use an "m" function as the function name. While I think that we could still use one m( ) function for both constructing and accessing matrices, I think it would be better to develop a separate sm( ) function to make initial development of a matrix accessor easier. Finally, using m( ) as the function used to create/access a matrix reminds me of JQuery's $() operator so I don't think there is anything inherently wrong with using a one letter function name if the importance of the operator supports reserving a letter for it. This is arguably the case for an operator designed to create and access matrices.

Permalink 

No comments entered ...

 Archive 
 

php/Math Project
© 2011. All rights reserved.