/***
* Name: Matrices
* Author: Benoit Gaudou
* Description: Examples of the syntax and various operators used to manipulate the 'matrix' data type. 
* Read the comments and run the model to get a better idea on how to use matrices in GAML. 
* Tags: matrix, loop
***/

model Matrices

species declaring_matrix_attributes {
	
	/**
	 * Declarations of matrix attributes
	 */
	 // The simplest declaration identifies empty_matrix as a matrix that can contain any type of objects. 
	 // Its default value will be [] (the empty matrix) if it is not initialized.
	matrix empty_matrix;
	// To provide it with an initial value, use the '<-' (or 'init:') facet
	matrix explicit_empty_matrix <- [];
	// matrices can also be provided with a default size, in which case they are filled with a given element
	matrix matrix_of_size_3_3_with_0 <- {3,3} matrix_with 0; // => [[0,0,0],[0,0,0],[0,0,0]]
	// matrices can be declared so that they only accept a given type of contents.
	// For instance, empty_matrix_of_int will only accept integer elements
	matrix empty_matrix_of_int ;	
	// matrices can be define explicitely by rows
	 matrix matrix_explicit <- matrix([[1,2,3],[9,8,7]]);	
	// the value passed to 'matrix_with' is verified and casted to the contents type of the matrix if necessary
	matrix matrix_of_int_size_3_3_filled_with_string<- matrix({3,3} matrix_with('1')); // matrix_of_int_size_3_3_filled_with_string is filled with the casting of '1' to int, i.e. 1
	matrix matrix_of_string_size_3_3_filled_with_string <- {3,3} matrix_with('1'); // while matrix_of_string_size_3_3_filled_with_string is filled with the string '1'
	// the casting is also realized if the matrix is initialized with a value
	matrix matrix_of_int_with_init_of_string <- matrix([['10', '20'],['30','40']]); // => [[10,20],[30,40]]
	matrix matrix_of_float_with_init_of_string <- matrix(matrix_of_string_size_3_3_filled_with_string); // => [[1.0,1.0,1.0],[1.0,1.0,1.0],[1.0,1.0,1.0]]
	// When the casting is not obvious, the default value is used
	matrix matrix_of_float_with_impossible_casting <- matrix([['A','B'],['C','D']]);   // => [[0.0,0.0],[0.0,0.0]]
	// matrices can of course contain lists
	matrix matrix_of_lists <- matrix({5,5} matrix_with [1,2]);
	// matrices can of course contain matrices
	matrix matrix_of_matrices <- matrix({5,5} matrix_with matrix([[1],[2]])) ;
	// untyped matrixs can contain heterogeneous objects
	matrix untyped_matrix <- matrix([['5',5],[5,true]]);
	// the casting applies to all elements when a contents type is defined (note the default last value of 0)
	matrix recasted_matrix_with_int <- matrix(untyped_matrix); //=> [[5,5],[5,1]]
	// Matrices can also been created from other types, such as a list
	matrix matrix_of_string_from_list_3_2 <- ['A','B','C','D','E','F','G'] as_matrix {3,2};
	// When the requested dimension exceeds the number of available elements, the empty cells are filled with a nil value.
	matrix matrix_of_string_from_list_3_3 <- ['A','B','C','D','E','F','G'] as_matrix {3,3};	
	
	init {
		write "";
		write "== DECLARING MATRICES ==";
		write "";
		write sample(empty_matrix);
		write sample(explicit_empty_matrix);
		write sample(matrix_of_size_3_3_with_0);
		write sample(empty_matrix_of_int);
		write sample(matrix_explicit);
		write sample(matrix_of_int_size_3_3_filled_with_string);
		write sample(matrix_of_string_size_3_3_filled_with_string);
		write sample(matrix_of_int_with_init_of_string);
		write sample(matrix_of_float_with_init_of_string);
		write sample(matrix_of_float_with_impossible_casting);
		write sample(matrix_of_lists);
		write sample(matrix_of_matrices);
		write sample(untyped_matrix);
		write sample(recasted_matrix_with_int);
		write sample(matrix_of_string_from_list_3_2);		
		write sample(matrix_of_string_from_list_3_3);
		write "";

		// Matrices are not always declared litterally and can be obtained from various elements
		// by using the casting 'matrix()' operator
		// for instance, matrix(species_name) will return a matrix of all the instances of species_name (as a matrix of one row.
		create test_species number:5;
		matrix matrix_my_agents <- matrix(test_species);
		write sample(matrix_my_agents);
//		matrix matrix_my_agents_2_2 <- test_species as_matrix {2,2};
//		write sample(matrix_my_agents_2_2);
		// A matrix can also been get from a csv_file (more specifically from its contents)		
		file my_csv_file <- csv_file("includes/iris_small.csv",",",float,true);
		matrix matrix_from_csv_file <- matrix(my_csv_file.contents);
		write sample(matrix_from_csv_file);
 	}

}

species test_species {}

species accessing_matrix_elements {
	matrix m1 <- matrix([[1,2,3,4],[5,6,7,8]]);
	matrix m2 <- ['this','is','a','matrix', 'of','strings'] as_matrix {3,2};
	matrix m_square <- matrix([[1,2,8],[4,12,6],[7,8,9]]);	
	
	init {
		write "";
		write "== ACCESSING MATRIX ELEMENTS ==";
		write "";
		write sample(m1);
		write sample(m2);
		write sample(first(m1));
		write sample(last(m1));
		// Matrices are indexed by a point 
		write sample(m1 at {0,0});
		write sample(m1[{1,3}]);
		write sample(length(m1));
		write sample(mean(m1));
		write sample(max(m1));
		write sample(min(m1));
		write sample(any(m1));
		write sample(3 among m2);
		write sample(m1 contains 1);
		write sample(m1 contains_all [1,4,6, 14]);
		write sample(m1 contains_any [1,23]);
		// reverse on a matrix is a transpose
		write sample(reverse(m2));
		// collect transforms a matrix into a list
		write sample(m1 collect (each + 1));
		// we thus need to transform again the result list to get a matrix
		write sample(m1 collect (each + 1) as_matrix {2,4});
		write sample(m1 collect (norm({each, each, each})));
		write sample(m1 where (each > 5));
		write sample(m1 count (each > 5));
		write sample(m1 group_by (even(each)));
		write sample(m2 index_by (each + "_index"));
		write sample(m1 index_of 2);
		write sample(m2 last_index_of 'is');
		write sample(m2 sort_by each);
		write sample(m2 sort_by length(each));
		write sample(m2 first_with (first(each)  = 'o'));
		write sample(m2 where (length(each) = 2) );
		write sample(m2 with_min_of (length(each)));
		write sample(m2 with_max_of (length(each)));
		write sample(m2 min_of (length(each)));
		write sample(m2 max_of (length(each))); 
		write sample(m2 as_map (length(each)::"new"+each));
		
		// Rows (resp. columns) can also be accessed
		write sample(columns_list(m1));
		write sample(rows_list(m1));		
		write sample(m1 row_at 1);
		write sample(m1 column_at 1);
		
		// Some classical operators of matrix computation have been introduced
		write sample(det(m_square));
		write sample(determinant(m_square));
		write sample(eigenvalues(m_square));
		write sample(inverse(m_square));
		write sample(trace(m_square)); 
		write sample(transpose(m_square));
		
	}
}

species combining_matrices {
	matrix m1 <- matrix([[1,2,3],[4,5,6],[7,8,9]]);
	matrix m2 <- [1,3,5,7,9,11] as_matrix {3,2};
	
	init {
		write "";
		write "== COMBINING MATRICES ==";
		write "";
		write sample(m1);
		write sample(rows_list(m1));
		write sample(m2);
		write sample(rows_list(m2));		
		write sample(m1 + m1);
		write sample(m2 - m2);
		// inter between 2 matrices returns the list of all the elements part of both matrices.
		write sample(m1 inter m2);
		// union between 2 matrices returns the list of all the elements part at least in one of the two matrices.
		write sample(m1 union m2);
		write sample(interleave ([m1,m1]));
		matrix m3 <- matrix(m1 + m1);
		write "matrix m3 <- m1 + m2; " + sample(m3);
		write sample(m1 as list);
		write sample(2 * m1);
		write sample(2 + m1);
		
		write sample(m1 * m1);
		write sample(m1 / m1);
		
		write sample(m1 append_horizontally m1);			
		// Notice that when the 2 matrices do not have the same number of rows, m2 is considered as being 3x3.
		// The matrix is completed by 0 and thus becomes matrix([[1,9,0],[7,5,0],[3,11,0]])
		write sample(m1 append_horizontally m2);
		// m1 dimension is set to the m2 dimension
		write sample(m2 append_horizontally m1);		
		write sample(m1 append_vertically m1);
		write sample(m1 append_vertically m2);

		
		// Some combinations of matrices are not possible
		write "Following computations have errors due to incompatible sizes";
		try {
			write sample(m1 + m2);			
		} catch { 
			write "m1 + m2 : " + m1 + " -- " + m2 + " are not compatibble to sum.";
		}
	}
}

species looping_on_matrices {
	init {
		write "";
		write "== LOOPING ON MATRICES ==";
		write "";
		// Besides iterator operators (like "collect", "where", etc.), which provide 
		// functional iterations (i.e. filters), one can loop over matrices using the imperative
		// statement 'loop'
	    matrix matrix_of_strings <- matrix([["A","matrix"],["of","strings"]]);
		write sample(matrix_of_strings);
		
		int i <- 0;
		// Here, the value of 's' will be that added to each element of the matrix
		loop s over: matrix_of_strings {
			i <- i + 1;
			write "Word #" + i + ": " + s;
		}

		// 'loop' can also directly use two integer indices (remember matrices have a zero-based index)
		loop index_row from: 0 to: matrix_of_strings.rows - 1 {
			loop index_column from: 0 to: matrix_of_strings.columns - 1 {
				write "The element at row: " + (index_row+1) + " and column: " + (index_column+1) + " of the matrix is: " + matrix_of_strings[index_column,index_row];				
			}
		}		
	}
}

species modifying_matrices {
	init {
		write "";
		write "== MODIFYING LISTS ==";
		write "";
		trace {
			// Besides assigning a new value to a matrix, matrices can be manipulated using
			// the "put statements. 
			// Notice that they have a fix size (number of elements). 
			// As a consequence, add and remove cannot be used on a matrix.
		    matrix matrix_of_strings <- matrix([["A","matrix"],["of","strings"]]);
			write sample(matrix_of_strings);
			put "Two" in: matrix_of_strings at: {0,0};	
			put "matrices" in: matrix_of_strings at: {0,1};			
			write sample(matrix_of_strings);
			
			// The two previous put called can be replaced bby an assignement 
			// Let revert the previous modifications
			matrix_of_strings[{0,0}] <- "A";	
			matrix_of_strings[{0,1}] <- "matrix";	
			
			write sample(matrix_of_strings);
			
			// All the values can also be replaced
			// Let set all the values in the matrix to empty string
			put "" in: matrix_of_strings all: true;
			write sample(matrix_of_strings);
		}
	}
}

experiment Matrices type: gui {
	user_command "Declaring matrices" {create declaring_matrix_attributes;}	
	user_command "Accessing matrix elements " {create accessing_matrix_elements;}	
	user_command "Combining Matrices " {create combining_matrices;}	
	user_command "Modifying Matrices" {create modifying_matrices;}
	user_command "Looping on Matrices " {create looping_on_matrices;}
}