From c7860b40e39596db2160c0c26e5929d0bca25b6b Mon Sep 17 00:00:00 2001 From: drbergman Date: Sun, 2 Feb 2025 06:11:44 -0500 Subject: [PATCH 1/4] DC initialization from file provide a CSV file with similar structure to the substrate ICs csv header row: x,y,z,,,... where the list of substrates need not be all the substrates, only those with DCs being set each subsequent row is: ,,,,,... where the coords must be specified and the vals can either be empty (,,,...) or a number If empty, then nothing chagnes for the substrate in that column at that voxel If a number, then the DC activation for that voxel-substrate pairing is set to the number --- .gitignore | 1 + BioFVM/BioFVM_microenvironment.cpp | 307 +++++++++++++---- BioFVM/BioFVM_microenvironment.h | 16 +- BioFVM/BioFVM_vector.cpp | 55 +++ BioFVM/BioFVM_vector.h | 1 + Makefile | 10 +- modules/PhysiCell_settings.cpp | 15 + sample_projects/Makefile-default | 10 +- sample_projects/dirichlet_from_file/Makefile | 323 ++++++++++++++++++ .../config/PhysiCell_settings.xml | 276 +++++++++++++++ .../dirichlet_from_file/config/cell_rules.csv | 1 + .../dirichlet_from_file/config/cells.csv | 1 + .../dirichlet_from_file/config/dcs.csv | 81 +++++ .../custom_modules/custom.cpp | 214 ++++++++++++ .../custom_modules/custom.h | 92 +++++ sample_projects/dirichlet_from_file/main.cpp | 254 ++++++++++++++ 16 files changed, 1588 insertions(+), 69 deletions(-) create mode 100644 sample_projects/dirichlet_from_file/Makefile create mode 100644 sample_projects/dirichlet_from_file/config/PhysiCell_settings.xml create mode 100644 sample_projects/dirichlet_from_file/config/cell_rules.csv create mode 100644 sample_projects/dirichlet_from_file/config/cells.csv create mode 100644 sample_projects/dirichlet_from_file/config/dcs.csv create mode 100644 sample_projects/dirichlet_from_file/custom_modules/custom.cpp create mode 100644 sample_projects/dirichlet_from_file/custom_modules/custom.h create mode 100644 sample_projects/dirichlet_from_file/main.cpp diff --git a/.gitignore b/.gitignore index 765ccdc8c..63546c554 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ user_projects/* !user_projects/empty.txt Studio.zip /studio +.vscode \ No newline at end of file diff --git a/BioFVM/BioFVM_microenvironment.cpp b/BioFVM/BioFVM_microenvironment.cpp index e18c9ff43..17353d671 100644 --- a/BioFVM/BioFVM_microenvironment.cpp +++ b/BioFVM/BioFVM_microenvironment.cpp @@ -228,6 +228,12 @@ bool& Microenvironment::is_dirichlet_node( int voxel_index ) return mesh.voxels[voxel_index].is_Dirichlet; } +void Microenvironment::set_only_substrate_dirichlet_activation( int substrate_index , bool new_value ) +{ + dirichlet_activation_vector[substrate_index] = new_value; + return; +} + void Microenvironment::set_substrate_dirichlet_activation( int substrate_index , bool new_value ) { dirichlet_activation_vector[substrate_index] = new_value; @@ -1225,6 +1231,195 @@ void set_microenvironment_initial_condition( void ) { microenvironment.density_vector(n) = default_microenvironment_options.initial_condition_vector; } } + // set the dirichlet condition + set_dirichlet_initial_condition(); + + microenvironment.display_information(std::cout); + return; +} + +void load_initial_conditions_from_matlab(std::string filename) +{ + // The .mat file needs to contain exactly one matrix where each row corresponds to a single voxel. + // Each row is a vector of values as follows: [x coord, y coord, z coord, substrate id 0 value, substrate id 1 value, ...] + // Thus, your matrix should be of size #voxels x (3 + #densities) (rows x columns) + // M will be read in such that each element of M is one of these rows + std::vector< std::vector > M = read_matlab( filename ); + int num_mat_voxels = M.size(); + int num_mat_features = M[1].size(); + if (num_mat_voxels != microenvironment.number_of_voxels()) + { + std::cout << "ERROR : Wrong number of voxels supplied in the .mat file specifying BioFVM initial conditions." << std::endl + << "\tExpected: " << microenvironment.number_of_voxels() << std::endl + << "\tFound: " << num_mat_voxels << std::endl + << "\tRemember, save your matrix as a #voxels x (3 + #densities) matrix." << std::endl; + exit(-1); + } + if (num_mat_features != (microenvironment.number_of_densities() + 3)) + { + std::cout << "ERROR : Wrong number of density values supplied in the .mat file specifying BioFVM initial conditions." << std::endl + << "\tExpected: " << microenvironment.number_of_densities() << std::endl + << "\tFound: " << num_mat_features - 3 << std::endl + << "\tRemember, save your matrix as a #voxels x (3 + #densities) matrix." << std::endl; + exit(-1); + } + std::vector voxel_set = {}; // set to check that no voxel value is set twice + int voxel_ind; + std::vector position; + for (unsigned int i = 0; i < num_mat_voxels; i++) + { + position = {M[i][0], M[i][1], M[i][2]}; + voxel_ind = microenvironment.mesh.nearest_voxel_index(position); + for (unsigned int ci = 0; ci < microenvironment.number_of_densities(); ci++) + { + microenvironment.density_vector(voxel_ind)[ci] = M[i][ci+3]; + } + for (unsigned int j = 0; j < i; j++) + { + if (voxel_ind==voxel_set[j]) + { + std::cerr << "ERROR : the matlab-supplied initial conditions for BioFVM repeat the same voxel. Fix the .mat file and try again." << std::endl + << "\tPosition that was repeated: " << position << std::endl; + exit(-1); + } + } + voxel_set.push_back(voxel_ind); + } + + return; +} + +void load_initial_conditions_from_csv(std::string filename) +{ + // The .csv file needs to contain one row per voxel. + // Each row is a vector of values as follows: [x coord, y coord, z coord, substrate id 0 value, substrate id 1 value, ...] + // Thus, your table should be of size #voxels x (3 + #densities) (rows x columns) + + // open file + std::ifstream file( filename, std::ios::in ); + if( !file ) + { + std::cout << "ERROR: " << filename << " not found during cell loading. Quitting." << std::endl; + exit(-1); + } + + // determine if header row exists + std::string line; + std::getline( file , line ); + char c = line.c_str()[0]; + std::vector substrate_indices; + bool header_provided = false; + if( c == 'X' || c == 'x' ) + { + // do not support this with a header yet + if ((line.c_str()[2] != 'Y' && line.c_str()[2] != 'y') || (line.c_str()[4] != 'Z' && line.c_str()[4] != 'z')) + { + std::cout << "ERROR: Header row starts with x but then not y,z? What is this? Exiting now." << std::endl; + file.close(); + exit(-1); + } + std::vector< std::string> column_names; // this will include x,y,z (so make sure to skip those below) + std::stringstream stream(line); + std::string field; + + while (std::getline(stream, field, ',')) + { + column_names.push_back(field); + } + for (int i = 3; i voxel_set = {}; // set to check that no voxel value is set twice + + while (std::getline(file, line)) + { + get_row_from_substrate_initial_condition_csv(voxel_set, line, substrate_indices, header_provided); + } + + if (voxel_set.size() != microenvironment.number_of_voxels()) + { + std::cout << "ERROR : Wrong number of voxels supplied in the .csv file specifying BioFVM initial conditions." << std::endl + << "\tExpected: " << microenvironment.number_of_voxels() << std::endl + << "\tFound: " << voxel_set.size() << std::endl + << "\tRemember, your table should have dimensions #voxels x (3 + #densities)." << std::endl; + exit(-1); + } + + file.close(); + + return; +} + +void get_row_from_substrate_initial_condition_csv(std::vector &voxel_set, const std::string line, const std::vector substrate_indices, const bool header_provided) +{ + static bool warning_issued = false; + std::vector data; + csv_to_vector(line.c_str(), data); + + if (!(warning_issued) && !(header_provided) && (data.size() != (microenvironment.number_of_densities() + 3))) + { + std::cout << "WARNING: Wrong number of density values supplied in the .csv file specifying BioFVM initial conditions." << std::endl + << "\tExpected: " << microenvironment.number_of_voxels() << std::endl + << "\tFound: " << data.size() - 3 << std::endl + << "\tRemember, save your csv with columns as: x, y, z, substrate_0, substrate_1,...." << std::endl + << "\tThis could also be resolved by including a header row \"x,y,z,[substrate_i0,substrate_i1]\"" << std::endl; + warning_issued = true; + } + + std::vector position = {data[0], data[1], data[2]}; + int voxel_ind = microenvironment.mesh.nearest_voxel_index(position); + for (unsigned int ci = 0; ci < substrate_indices.size(); ci++) // column index, counting from the first substrate (or just the index of the vector substrate_indices) + { + microenvironment.density_vector(voxel_ind)[substrate_indices[ci]] = data[ci + 3]; + } + for (unsigned int j = 0; j < voxel_set.size(); j++) + { + if (voxel_ind == voxel_set[j]) + { + std::cout << "ERROR : the csv-supplied initial conditions for BioFVM repeat the same voxel. Fix the .csv file and try again." << std::endl + << "\tPosition that was repeated: " << position << std::endl; + exit(-1); + } + } + voxel_set.push_back(voxel_ind); +} + +void set_dirichlet_initial_condition( void ) +{ + set_dirichlet_boundaries_from_XML(); + set_dirichlet_boundaries_from_file(); +} + +void set_dirichlet_boundaries_from_XML( void ) +{ // now, figure out which sides have BCs (for at least one substrate): bool xmin = false; @@ -1391,70 +1586,48 @@ void set_microenvironment_initial_condition( void ) } } } - } - - microenvironment.display_information(std::cout); - return; } -void load_initial_conditions_from_matlab(std::string filename) +void set_dirichlet_boundaries_from_file( void ) { - // The .mat file needs to contain exactly one matrix where each row corresponds to a single voxel. - // Each row is a vector of values as follows: [x coord, y coord, z coord, substrate id 0 value, substrate id 1 value, ...] - // Thus, your matrix should be of size #voxels x (3 + #densities) (rows x columns) - // M will be read in such that each element of M is one of these rows - std::vector< std::vector > M = read_matlab( filename ); - int num_mat_voxels = M.size(); - int num_mat_features = M[1].size(); - if (num_mat_voxels != microenvironment.number_of_voxels()) + if (!default_microenvironment_options.dirichlet_condition_from_file_enabled) { - std::cout << "ERROR : Wrong number of voxels supplied in the .mat file specifying BioFVM initial conditions." << std::endl - << "\tExpected: " << microenvironment.number_of_voxels() << std::endl - << "\tFound: " << num_mat_voxels << std::endl - << "\tRemember, save your matrix as a #voxels x (3 + #densities) matrix." << std::endl; - exit(-1); + return; } - if (num_mat_features != (microenvironment.number_of_densities() + 3)) + std::string filetype = default_microenvironment_options.dirichlet_condition_file_type; + std::string filename = default_microenvironment_options.dirichlet_condition_file; + if (filetype == "matlab") { - std::cout << "ERROR : Wrong number of density values supplied in the .mat file specifying BioFVM initial conditions." << std::endl - << "\tExpected: " << microenvironment.number_of_densities() << std::endl - << "\tFound: " << num_mat_features - 3 << std::endl - << "\tRemember, save your matrix as a #voxels x (3 + #densities) matrix." << std::endl; - exit(-1); + load_dirichlet_conditions_from_matlab(filename); } - std::vector voxel_set = {}; // set to check that no voxel value is set twice - int voxel_ind; - std::vector position; - for (unsigned int i = 0; i < num_mat_voxels; i++) + else if (filetype == "csv" || filetype == "CSV") { - position = {M[i][0], M[i][1], M[i][2]}; - voxel_ind = microenvironment.mesh.nearest_voxel_index(position); - for (unsigned int ci = 0; ci < microenvironment.number_of_densities(); ci++) - { - microenvironment.density_vector(voxel_ind)[ci] = M[i][ci+3]; - } - for (unsigned int j = 0; j < i; j++) - { - if (voxel_ind==voxel_set[j]) - { - std::cout << "ERROR : the matlab-supplied initial conditions for BioFVM repeat the same voxel. Fix the .mat file and try again." << std::endl - << "\tPosition that was repeated: " << position << std::endl; - exit(-1); - } - } - voxel_set.push_back(voxel_ind); + load_dirichlet_conditions_from_csv(filename); + } + else // eventually can add other file types + { + std::cout << "ERROR : Load BioFVM dirichlet conditions from " << filetype << " not yet supported." << std::endl; + exit(-1); } - return; } -void load_initial_conditions_from_csv(std::string filename) +void load_dirichlet_conditions_from_matlab(std::string filename) +{ + std::cerr << "ERROR: Load BioFVM dirichlet conditions from MATLAB not yet supported." << std::endl; + exit(-1); +} + +void load_dirichlet_conditions_from_csv(std::string filename) { // The .csv file needs to contain one row per voxel. // Each row is a vector of values as follows: [x coord, y coord, z coord, substrate id 0 value, substrate id 1 value, ...] // Thus, your table should be of size #voxels x (3 + #densities) (rows x columns) - // Do not include a header row. + + // alternatively, include a header row that begins "x,y,z" and then lists the substrate names to have DC values set + + // In either case, an empty value in the table will be interpreted as not changing the DC condition for that voxel-substrate pair // open file std::ifstream file( filename, std::ios::in ); @@ -1470,6 +1643,7 @@ void load_initial_conditions_from_csv(std::string filename) char c = line.c_str()[0]; std::vector substrate_indices; bool header_provided = false; + int n_cols; if( c == 'X' || c == 'x' ) { // do not support this with a header yet @@ -1499,6 +1673,7 @@ void load_initial_conditions_from_csv(std::string filename) substrate_indices.push_back(microenvironment.find_density_index(column_names[i])); } header_provided = true; + n_cols = column_names.size(); } else // no column labels given; just assume that the first n substrates are supplied (n = # columns after x,y,z) { @@ -1512,42 +1687,38 @@ void load_initial_conditions_from_csv(std::string filename) i++; } // in this case, we want to read this first line, so close the file and re-open so that we start with this line + n_cols = 3 + substrate_indices.size(); file.close(); std::ifstream file(filename, std::ios::in); std::getline(file, line); } - std::cout << "Loading substrate initial conditions from CSV file " << filename << " ... " << std::endl; + std::cout << "Loading substrate dirichlet conditions from CSV file " << filename << " ... " << std::endl; std::vector voxel_set = {}; // set to check that no voxel value is set twice - + while (std::getline(file, line)) { - get_row_from_substrate_initial_condition_csv(voxel_set, line, substrate_indices, header_provided); - } - - if (voxel_set.size() != microenvironment.number_of_voxels()) - { - std::cout << "ERROR : Wrong number of voxels supplied in the .csv file specifying BioFVM initial conditions." << std::endl - << "\tExpected: " << microenvironment.number_of_voxels() << std::endl - << "\tFound: " << voxel_set.size() << std::endl - << "\tRemember, your table should have dimensions #voxels x (3 + #densities)." << std::endl; - exit(-1); + get_row_from_dirichlet_condition_csv(voxel_set, line, substrate_indices, header_provided, n_cols); } - file.close(); - + file.close(); + return; } -void get_row_from_substrate_initial_condition_csv(std::vector &voxel_set, const std::string line, const std::vector substrate_indices, const bool header_provided) +void get_row_from_dirichlet_condition_csv(std::vector &voxel_set, const std::string line, const std::vector substrate_indices, const bool header_provided, int n_cols) { static bool warning_issued = false; + std::vector is_missing; std::vector data; - csv_to_vector(line.c_str(), data); + is_missing.resize(n_cols); + data.resize(n_cols); + + dirichlet_csv_to_vector(line.c_str(), is_missing, data); if (!(warning_issued) && !(header_provided) && (data.size() != (microenvironment.number_of_densities() + 3))) { - std::cout << "WARNING: Wrong number of density values supplied in the .csv file specifying BioFVM initial conditions." << std::endl + std::cout << "WARNING: Wrong number of density data supplied in the .csv file specifying BioFVM dirichlet conditions." << std::endl << "\tExpected: " << microenvironment.number_of_voxels() << std::endl << "\tFound: " << data.size() - 3 << std::endl << "\tRemember, save your csv with columns as: x, y, z, substrate_0, substrate_1,...." << std::endl @@ -1559,13 +1730,17 @@ void get_row_from_substrate_initial_condition_csv(std::vector &voxel_set, c int voxel_ind = microenvironment.mesh.nearest_voxel_index(position); for (unsigned int ci = 0; ci < substrate_indices.size(); ci++) // column index, counting from the first substrate (or just the index of the vector substrate_indices) { - microenvironment.density_vector(voxel_ind)[substrate_indices[ci]] = data[ci + 3]; + if (!is_missing[ci + 3]) + { + microenvironment.update_dirichlet_node(voxel_ind, substrate_indices[ci], data[ci + 3]); + microenvironment.set_only_substrate_dirichlet_activation(substrate_indices[ci], true); + } } for (unsigned int j = 0; j < voxel_set.size(); j++) { if (voxel_ind == voxel_set[j]) { - std::cout << "ERROR : the csv-supplied initial conditions for BioFVM repeat the same voxel. Fix the .csv file and try again." << std::endl + std::cout << "ERROR : the csv-supplied dirichlet conditions for BioFVM repeat the same voxel. Fix the .csv file and try again." << std::endl << "\tPosition that was repeated: " << position << std::endl; exit(-1); } diff --git a/BioFVM/BioFVM_microenvironment.h b/BioFVM/BioFVM_microenvironment.h index 6fec93728..fb5d7354f 100644 --- a/BioFVM/BioFVM_microenvironment.h +++ b/BioFVM/BioFVM_microenvironment.h @@ -254,6 +254,9 @@ class Microenvironment void set_substrate_dirichlet_activation( int index, std::vector& new_value ); bool get_substrate_dirichlet_activation( int substrate_index, int index ); + // only change dirichlet_activation_vector, not the activation_vectors for each voxel + void set_only_substrate_dirichlet_activation( int substrate_index , bool new_value ); + double get_substrate_dirichlet_value( int substrate_index, int index ); bool& is_dirichlet_node( int voxel_index ); @@ -341,9 +344,13 @@ class Microenvironment_Options std::vector initial_condition_vector; - bool initial_condition_from_file_enabled; + bool initial_condition_from_file_enabled = false; std::string initial_condition_file_type; std::string initial_condition_file; + + bool dirichlet_condition_from_file_enabled = false; + std::string dirichlet_condition_file_type; + std::string dirichlet_condition_file; bool simulate_2D; std::vector X_range; @@ -368,6 +375,13 @@ void set_microenvironment_initial_condition( void ); void load_initial_conditions_from_matlab( std::string filename ); void load_initial_conditions_from_csv( std::string filename ); void get_row_from_substrate_initial_condition_csv(std::vector &voxel_set, const std::string line, const std::vector substrate_indices, const bool header_provided); + +void set_dirichlet_initial_condition( void ); +void set_dirichlet_boundaries_from_XML( void ); +void set_dirichlet_boundaries_from_file( void ); +void load_dirichlet_conditions_from_matlab( std::string filename ); +void load_dirichlet_conditions_from_csv(std::string filename); +void get_row_from_dirichlet_condition_csv(std::vector &voxel_set, const std::string line, const std::vector substrate_indices, const bool header_provided, int n_cols); }; #endif diff --git a/BioFVM/BioFVM_vector.cpp b/BioFVM/BioFVM_vector.cpp index e12a3672d..6981fef9d 100644 --- a/BioFVM/BioFVM_vector.cpp +++ b/BioFVM/BioFVM_vector.cpp @@ -376,6 +376,61 @@ void csv_to_vector( const char* buffer , std::vector& vect ) return; } +void dirichlet_csv_to_vector( const char* buffer , std::vector& is_missing , std::vector& data ) +{ + size_t ind = 0; + unsigned int i=0; + std::string entry; + while( i < strlen( buffer ) ) + { + if(buffer[i] == ',') + { + if(entry.empty()) + { + is_missing[ind] = true; + } + else + { + data[ind] = std::stod(entry); + is_missing[ind] = false; + } + entry.clear(); + ind++; + } + else + { + entry += buffer[i]; + } + i++; + } + // Handle the last entry + if(entry.empty()) + { + is_missing[ind] = true; + } + else + { + data[ind] = std::stod(entry); + is_missing[ind] = false; + } + + if (is_missing[0] || is_missing[1] || is_missing[2]) + { + std::cerr << "Error: x, y, and z data must be provided for each row in the .csv file specifying BioFVM dirichlet conditions." << std::endl; + exit(-1); + } + + if (ind < is_missing.size() - 1) + { + std::cerr << "Error: Wrong number of data supplied in a row of the .csv file specifying BioFVM dirichlet conditions." << std::endl; + std::cerr << "\tExpected: " << is_missing.size() << ". Found: " << ind + 1 << std::endl; + std::cerr << "\tRow: " << buffer << std::endl; + exit(-1); + } + return; +} + + char* vector_to_csv( const std::vector& vect ) { static int datum_size = 16; // format = %.7e, 1 (sign) + 1 (lead) + 1 (decimal) + 7 (figs) + 2 (e, sign) + 3 (exponent) + 1 (delimiter) = 16 diff --git a/BioFVM/BioFVM_vector.h b/BioFVM/BioFVM_vector.h index e3c36d449..881239061 100644 --- a/BioFVM/BioFVM_vector.h +++ b/BioFVM/BioFVM_vector.h @@ -130,6 +130,7 @@ void double_axpy_div( std::vector* y, std::vector& a1 , std::vec // turn a delimited character array (e.g., csv) into a vector of doubles void csv_to_vector( const char* buffer , std::vector& vect ); +void dirichlet_csv_to_vector( const char* buffer , std::vector& missings , std::vector& values ); char* vector_to_csv( const std::vector& vect ); void vector_to_csv_safe( const std::vector& vect , char*& buffer ); void vector_to_csv( const std::vector& vect , char*& buffer ); diff --git a/Makefile b/Makefile index 5d256d1c3..14a9523f5 100644 --- a/Makefile +++ b/Makefile @@ -87,7 +87,7 @@ template: cp Makefile Makefile-backup cp ./sample_projects/template/Makefile . cp -r ./sample_projects/template/config/* ./config - + # sample projects # ---- non-intracellular projects @@ -233,6 +233,14 @@ episode-sample: cp ./sample_projects/episode/Makefile . cp -r ./sample_projects/episode/config/* ./config +dirichlet-from-file-sample: + cp -r ./sample_projects/dirichlet_from_file/custom_modules/* ./custom_modules/ + touch main.cpp && cp main.cpp main-backup.cpp + cp ./sample_projects/dirichlet_from_file/main.cpp ./main.cpp + cp Makefile Makefile-backup + cp ./sample_projects/dirichlet_from_file/Makefile . + cp -r ./sample_projects/dirichlet_from_file/config/* ./config + # ---- intracellular projects ode-energy-sample: cp ./sample_projects_intracellular/ode/ode_energy/custom_modules/* ./custom_modules/ diff --git a/modules/PhysiCell_settings.cpp b/modules/PhysiCell_settings.cpp index 538f6a8ea..5fcb6a505 100644 --- a/modules/PhysiCell_settings.cpp +++ b/modules/PhysiCell_settings.cpp @@ -965,6 +965,21 @@ bool setup_microenvironment_from_XML( pugi::xml_node root_node ) } } + node = xml_find_node( root_node , "microenvironment_setup" ); + node = xml_find_node( node , "options" ); + node = xml_find_node(node, "dirichlet_nodes"); + if (node) + { + default_microenvironment_options.dirichlet_condition_from_file_enabled = node.attribute("enabled").as_bool(); + if (default_microenvironment_options.dirichlet_condition_from_file_enabled) + { + default_microenvironment_options.dirichlet_condition_file_type = node.attribute("type").as_string(); + default_microenvironment_options.dirichlet_condition_file = xml_get_string_value(node, "filename"); + + copy_file_to_output(default_microenvironment_options.dirichlet_condition_file); + } + } + // not yet supported : read initial conditions /* // read in initial conditions from an external file diff --git a/sample_projects/Makefile-default b/sample_projects/Makefile-default index 5d256d1c3..14a9523f5 100644 --- a/sample_projects/Makefile-default +++ b/sample_projects/Makefile-default @@ -87,7 +87,7 @@ template: cp Makefile Makefile-backup cp ./sample_projects/template/Makefile . cp -r ./sample_projects/template/config/* ./config - + # sample projects # ---- non-intracellular projects @@ -233,6 +233,14 @@ episode-sample: cp ./sample_projects/episode/Makefile . cp -r ./sample_projects/episode/config/* ./config +dirichlet-from-file-sample: + cp -r ./sample_projects/dirichlet_from_file/custom_modules/* ./custom_modules/ + touch main.cpp && cp main.cpp main-backup.cpp + cp ./sample_projects/dirichlet_from_file/main.cpp ./main.cpp + cp Makefile Makefile-backup + cp ./sample_projects/dirichlet_from_file/Makefile . + cp -r ./sample_projects/dirichlet_from_file/config/* ./config + # ---- intracellular projects ode-energy-sample: cp ./sample_projects_intracellular/ode/ode_energy/custom_modules/* ./custom_modules/ diff --git a/sample_projects/dirichlet_from_file/Makefile b/sample_projects/dirichlet_from_file/Makefile new file mode 100644 index 000000000..7aa7d18d2 --- /dev/null +++ b/sample_projects/dirichlet_from_file/Makefile @@ -0,0 +1,323 @@ +VERSION := $(shell grep . VERSION.txt | cut -f1 -d:) +PROGRAM_NAME := project + +CC := g++ +# CC := g++-mp-7 # typical macports compiler name +# CC := g++-7 # typical homebrew compiler name + +# Check for environment definitions of compiler +# e.g., on CC = g++-7 on OSX +ifdef PHYSICELL_CPP + CC := $(PHYSICELL_CPP) +endif + +ifndef STATIC_OPENMP + STATIC_OPENMP = -fopenmp +endif + +ARCH := native # best auto-tuning +# ARCH := core2 # a reasonably safe default for most CPUs since 2007 +# ARCH := corei7 +# ARCH := corei7-avx # earlier i7 +# ARCH := core-avx-i # i7 ivy bridge or newer +# ARCH := core-avx2 # i7 with Haswell or newer +# ARCH := nehalem +# ARCH := westmere +# ARCH := sandybridge # circa 2011 +# ARCH := ivybridge # circa 2012 +# ARCH := haswell # circa 2013 +# ARCH := broadwell # circa 2014 +# ARCH := skylake # circa 2015 +# ARCH := bonnell +# ARCH := silvermont +# ARCH := skylake-avx512 +# ARCH := nocona #64-bit pentium 4 or later + +# CFLAGS := -march=$(ARCH) -Ofast -s -fomit-frame-pointer -mfpmath=both -fopenmp -m64 -std=c++11 +CFLAGS := -march=$(ARCH) -O3 -fomit-frame-pointer -mfpmath=both -fopenmp -m64 -std=c++11 + +ifeq ($(OS),Windows_NT) +else + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S),Darwin) + UNAME_P := $(shell uname -p) + var := $(shell which $(CC) | xargs file) + ifeq ($(lastword $(var)),arm64) + CFLAGS := -march=$(ARCH) -O3 -fomit-frame-pointer -fopenmp -m64 -std=c++11 + endif + endif +endif + +CFLAGS_LINK := $(shell echo $(CFLAGS) | sed -e "s/-fopenmp//g") +COMPILE_COMMAND := $(CC) $(CFLAGS) $(EXTRA_FLAGS) +LINK_COMMAND := $(CC) $(CFLAGS_LINK) $(EXTRA_FLAGS) + +BioFVM_OBJECTS := BioFVM_vector.o BioFVM_mesh.o BioFVM_microenvironment.o BioFVM_solvers.o BioFVM_matlab.o \ +BioFVM_utilities.o BioFVM_basic_agent.o BioFVM_MultiCellDS.o BioFVM_agent_container.o + +PhysiCell_core_OBJECTS := PhysiCell_phenotype.o PhysiCell_cell_container.o PhysiCell_standard_models.o \ +PhysiCell_cell.o PhysiCell_custom.o PhysiCell_utilities.o PhysiCell_constants.o PhysiCell_basic_signaling.o \ +PhysiCell_signal_behavior.o PhysiCell_rules.o + +PhysiCell_module_OBJECTS := PhysiCell_SVG.o PhysiCell_pathology.o PhysiCell_MultiCellDS.o PhysiCell_various_outputs.o \ +PhysiCell_pugixml.o PhysiCell_settings.o PhysiCell_geometry.o + +# put your custom objects here (they should be in the custom_modules directory) + +PhysiCell_custom_module_OBJECTS := custom.o + +pugixml_OBJECTS := pugixml.o + +PhysiCell_OBJECTS := $(BioFVM_OBJECTS) $(pugixml_OBJECTS) $(PhysiCell_core_OBJECTS) $(PhysiCell_module_OBJECTS) +ALL_OBJECTS := $(PhysiCell_OBJECTS) $(PhysiCell_custom_module_OBJECTS) + +# compile the project + +all: main.cpp $(ALL_OBJECTS) + $(COMPILE_COMMAND) -o $(PROGRAM_NAME) $(ALL_OBJECTS) main.cpp + make name + +static: main.cpp $(ALL_OBJECTS) $(MaBoSS) + $(LINK_COMMAND) $(INC) -o $(PROGRAM_NAME) $(ALL_OBJECTS) main.cpp $(LIB) -static-libgcc -static-libstdc++ $(STATIC_OPENMP) + +name: + @echo "" + @echo "Executable name is" $(PROGRAM_NAME) + @echo "" + +# PhysiCell core components + +PhysiCell_phenotype.o: ./core/PhysiCell_phenotype.cpp + $(COMPILE_COMMAND) -c ./core/PhysiCell_phenotype.cpp + +PhysiCell_digital_cell_line.o: ./core/PhysiCell_digital_cell_line.cpp + $(COMPILE_COMMAND) -c ./core/PhysiCell_digital_cell_line.cpp + +PhysiCell_cell.o: ./core/PhysiCell_cell.cpp + $(COMPILE_COMMAND) -c ./core/PhysiCell_cell.cpp + +PhysiCell_cell_container.o: ./core/PhysiCell_cell_container.cpp + $(COMPILE_COMMAND) -c ./core/PhysiCell_cell_container.cpp + +PhysiCell_standard_models.o: ./core/PhysiCell_standard_models.cpp + $(COMPILE_COMMAND) -c ./core/PhysiCell_standard_models.cpp + +PhysiCell_utilities.o: ./core/PhysiCell_utilities.cpp + $(COMPILE_COMMAND) -c ./core/PhysiCell_utilities.cpp + +PhysiCell_custom.o: ./core/PhysiCell_custom.cpp + $(COMPILE_COMMAND) -c ./core/PhysiCell_custom.cpp + +PhysiCell_constants.o: ./core/PhysiCell_constants.cpp + $(COMPILE_COMMAND) -c ./core/PhysiCell_constants.cpp + +PhysiCell_signal_behavior.o: ./core/PhysiCell_signal_behavior.cpp + $(COMPILE_COMMAND) -c ./core/PhysiCell_signal_behavior.cpp + +PhysiCell_rules.o: ./core/PhysiCell_rules.cpp + $(COMPILE_COMMAND) -c ./core/PhysiCell_rules.cpp + +# BioFVM core components (needed by PhysiCell) + +BioFVM_vector.o: ./BioFVM/BioFVM_vector.cpp + $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_vector.cpp + +BioFVM_agent_container.o: ./BioFVM/BioFVM_agent_container.cpp + $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_agent_container.cpp + +BioFVM_mesh.o: ./BioFVM/BioFVM_mesh.cpp + $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_mesh.cpp + +BioFVM_microenvironment.o: ./BioFVM/BioFVM_microenvironment.cpp + $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_microenvironment.cpp + +BioFVM_solvers.o: ./BioFVM/BioFVM_solvers.cpp + $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_solvers.cpp + +BioFVM_utilities.o: ./BioFVM/BioFVM_utilities.cpp + $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_utilities.cpp + +BioFVM_basic_agent.o: ./BioFVM/BioFVM_basic_agent.cpp + $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_basic_agent.cpp + +BioFVM_matlab.o: ./BioFVM/BioFVM_matlab.cpp + $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_matlab.cpp + +BioFVM_MultiCellDS.o: ./BioFVM/BioFVM_MultiCellDS.cpp + $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_MultiCellDS.cpp + +pugixml.o: ./BioFVM/pugixml.cpp + $(COMPILE_COMMAND) -c ./BioFVM/pugixml.cpp + +# standard PhysiCell modules + +PhysiCell_SVG.o: ./modules/PhysiCell_SVG.cpp + $(COMPILE_COMMAND) -c ./modules/PhysiCell_SVG.cpp + +PhysiCell_pathology.o: ./modules/PhysiCell_pathology.cpp + $(COMPILE_COMMAND) -c ./modules/PhysiCell_pathology.cpp + +PhysiCell_MultiCellDS.o: ./modules/PhysiCell_MultiCellDS.cpp + $(COMPILE_COMMAND) -c ./modules/PhysiCell_MultiCellDS.cpp + +PhysiCell_various_outputs.o: ./modules/PhysiCell_various_outputs.cpp + $(COMPILE_COMMAND) -c ./modules/PhysiCell_various_outputs.cpp + +PhysiCell_pugixml.o: ./modules/PhysiCell_pugixml.cpp + $(COMPILE_COMMAND) -c ./modules/PhysiCell_pugixml.cpp + +PhysiCell_settings.o: ./modules/PhysiCell_settings.cpp + $(COMPILE_COMMAND) -c ./modules/PhysiCell_settings.cpp + +PhysiCell_basic_signaling.o: ./core/PhysiCell_basic_signaling.cpp + $(COMPILE_COMMAND) -c ./core/PhysiCell_basic_signaling.cpp + +PhysiCell_geometry.o: ./modules/PhysiCell_geometry.cpp + $(COMPILE_COMMAND) -c ./modules/PhysiCell_geometry.cpp + +# user-defined PhysiCell modules + +custom.o: ./custom_modules/custom.cpp + $(COMPILE_COMMAND) -c ./custom_modules/custom.cpp + +# cleanup + +reset: + rm -f *.cpp + cp ./sample_projects/Makefile-default Makefile + rm -f ./custom_modules/* + touch ./custom_modules/empty.txt + touch ALL_CITATIONS.txt + touch ./core/PhysiCell_cell.cpp + rm ALL_CITATIONS.txt + cp ./config/PhysiCell_settings-backup.xml ./config/PhysiCell_settings.xml + touch ./config/empty.csv + rm -f ./config/*.csv + +clean: + rm -f *.o + rm -f $(PROGRAM_NAME)* + +data-cleanup: + rm -rf ./output + mkdir ./output + touch ./output/empty.txt + +# archival + +checkpoint: + zip -r $$(date +%b_%d_%Y_%H%M).zip Makefile *.cpp *.h config/*.xml custom_modules/* + +zip: + zip -r latest.zip Makefile* *.cpp *.h BioFVM/* config/* core/* custom_modules/* matlab/* modules/* sample_projects/* + cp latest.zip $$(date +%b_%d_%Y_%H%M).zip + cp latest.zip VERSION_$(VERSION).zip + mv *.zip archives/ + +tar: + tar --ignore-failed-read -czf latest.tar Makefile* *.cpp *.h BioFVM/* config/* core/* custom_modules/* matlab/* modules/* sample_projects/* + cp latest.tar $$(date +%b_%d_%Y_%H%M).tar + cp latest.tar VERSION_$(VERSION).tar + mv *.tar archives/ + +unzip: + cp ./archives/latest.zip . + unzip latest.zip + +untar: + cp ./archives/latest.tar . + tar -xzf latest.tar + +# easier animation + +FRAMERATE := 24 +OUTPUT := output + +jpeg: + @magick identify -format "%h" $(OUTPUT)/initial.svg > __H.txt + @magick identify -format "%w" $(OUTPUT)/initial.svg > __W.txt + @expr 2 \* \( $$(grep . __H.txt) / 2 \) > __H1.txt + @expr 2 \* \( $$(grep . __W.txt) / 2 \) > __W1.txt + @echo "$$(grep . __W1.txt)!x$$(grep . __H1.txt)!" > __resize.txt + @magick mogrify -format jpg -resize $$(grep . __resize.txt) $(OUTPUT)/s*.svg + rm -f __H*.txt __W*.txt __resize.txt + +gif: + magick convert $(OUTPUT)/s*.svg $(OUTPUT)/out.gif + +movie: + ffmpeg -r $(FRAMERATE) -f image2 -i $(OUTPUT)/snapshot%08d.jpg -vcodec libx264 -pix_fmt yuv420p -strict -2 -tune animation -crf 15 -acodec none $(OUTPUT)/out.mp4 + +# upgrade rules + +SOURCE := PhysiCell_upgrade.zip +get-upgrade: + @echo $$(curl https://raw.githubusercontent.com/MathCancer/PhysiCell/master/VERSION.txt) > VER.txt + @echo https://github.com/MathCancer/PhysiCell/releases/download/$$(grep . VER.txt)/PhysiCell_V.$$(grep . VER.txt).zip > DL_FILE.txt + rm -f VER.txt + $$(curl -L $$(grep . DL_FILE.txt) --output PhysiCell_upgrade.zip) + rm -f DL_FILE.txt + +PhysiCell_upgrade.zip: + make get-upgrade + +upgrade: $(SOURCE) + unzip $(SOURCE) PhysiCell/VERSION.txt + mv -f PhysiCell/VERSION.txt . + unzip $(SOURCE) PhysiCell/core/* + cp -r PhysiCell/core/* core + unzip $(SOURCE) PhysiCell/modules/* + cp -r PhysiCell/modules/* modules + unzip $(SOURCE) PhysiCell/sample_projects/* + cp -r PhysiCell/sample_projects/* sample_projects + unzip $(SOURCE) PhysiCell/BioFVM/* + cp -r PhysiCell/BioFVM/* BioFVM + unzip $(SOURCE) PhysiCell/documentation/User_Guide.pdf + mv -f PhysiCell/documentation/User_Guide.pdf documentation + rm -f -r PhysiCell + rm -f $(SOURCE) + +# use: make save PROJ=your_project_name +PROJ := my_project + +save: + echo "Saving project as $(PROJ) ... " + mkdir -p ./user_projects + mkdir -p ./user_projects/$(PROJ) + mkdir -p ./user_projects/$(PROJ)/custom_modules + mkdir -p ./user_projects/$(PROJ)/config + cp main.cpp ./user_projects/$(PROJ) + cp Makefile ./user_projects/$(PROJ) + cp VERSION.txt ./user_projects/$(PROJ) + cp ./config/* ./user_projects/$(PROJ)/config + cp ./custom_modules/* ./user_projects/$(PROJ)/custom_modules + +load: + echo "Loading project from $(PROJ) ... " + cp ./user_projects/$(PROJ)/main.cpp . + cp ./user_projects/$(PROJ)/Makefile . + cp ./user_projects/$(PROJ)/config/* ./config/ + cp ./user_projects/$(PROJ)/custom_modules/* ./custom_modules/ + +pack: + @echo " " + @echo "Preparing project $(PROJ) for sharing ... " + @echo " " + cd ./user_projects && zip -r $(PROJ).zip $(PROJ) + @echo " " + @echo "Share ./user_projects/$(PROJ).zip ... " + @echo "Other users can unzip $(PROJ).zip in their ./user_projects, compile, and run." + @echo " " + +unpack: + @echo " " + @echo "Preparing shared project $(PROJ).zip for use ... " + @echo " " + cd ./user_projects && unzip $(PROJ).zip + @echo " " + @echo "Load this project via make load PROJ=$(PROJ) ... " + @echo " " + +list-user-projects: + @echo "user projects::" + @cd ./user_projects && ls -dt1 * | grep . | sed 's!empty.txt!!' diff --git a/sample_projects/dirichlet_from_file/config/PhysiCell_settings.xml b/sample_projects/dirichlet_from_file/config/PhysiCell_settings.xml new file mode 100644 index 000000000..1aa7aff2a --- /dev/null +++ b/sample_projects/dirichlet_from_file/config/PhysiCell_settings.xml @@ -0,0 +1,276 @@ + + + + -500 + 500 + -500 + 500 + -10 + 10 + 20 + 20 + 20 + true + + + + 7200 + min + micron + 0.01 + 0.1 + 6 + + + + 6 + + + + output + + 60 + true + + + 60 + true + + substrate + YlOrRd + 0 + 1 + + + + false + + + + + false + true + false + 0 + + + + + + 0 + 10 + + 0 + 0 + + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + 0.0 + 0.0 + + 0.0 + 0.0 + + + + + + + + + + + + true + true + + ./config/initial.mat + + + ./config/dcs.csv + + + + + + + + + + 300.0 + 480.0 + 240.0 + 60.0 + + 1.0 + + + + 5.31667e-05 + + 516 + + + 0.05 + 0 + 1.66667e-02 + 5.83333e-03 + 0 + 2.0 + + + + 0.0 + + 0 + 86400 + + + 1.11667e-2 + 8.33333e-4 + 5.33333e-5 + 2.16667e-3 + 0 + 2.0 + + + + + + 2494 + 0.75 + 540 + 0.05 + 0.0045 + 0.0055 + 0 + 0 + 2.0 + + + + 0.4 + 10.0 + 1.25 + + 1 + + + 1.8 + 15.12 + + 0.01 + 0.0 + 0.0 + 12 + + + + 1 + 1 + .5 + + false + true + + false + substrate + 1 + + + false + false + + 0.0 + 0.0 + + + + + + + + 0 + 1 + 0 + 0 + + + 0.0 + 1.0 + 0.0 + 0.0 + + + + + 0 + 0 + 0 + + 0 + + + + 0 + + + 1 + 0.1 + + 0 + + + + + + + 0 + + + + + 0.0 + 0.0 + + + + + 1.0 + + + Volume42100000apoptosis1e-61e-2 + + + + + + + ./config + cells.csv + + + + + + + ./config + cell_rules.csv + + + + + + + 5 + + \ No newline at end of file diff --git a/sample_projects/dirichlet_from_file/config/cell_rules.csv b/sample_projects/dirichlet_from_file/config/cell_rules.csv new file mode 100644 index 000000000..078426a60 --- /dev/null +++ b/sample_projects/dirichlet_from_file/config/cell_rules.csv @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/sample_projects/dirichlet_from_file/config/cells.csv b/sample_projects/dirichlet_from_file/config/cells.csv new file mode 100644 index 000000000..734bdc767 --- /dev/null +++ b/sample_projects/dirichlet_from_file/config/cells.csv @@ -0,0 +1 @@ +0,0,0,0 \ No newline at end of file diff --git a/sample_projects/dirichlet_from_file/config/dcs.csv b/sample_projects/dirichlet_from_file/config/dcs.csv new file mode 100644 index 000000000..ab5abfe3b --- /dev/null +++ b/sample_projects/dirichlet_from_file/config/dcs.csv @@ -0,0 +1,81 @@ +x,y,z,substrate,oxygen +-210,-390,0.0,78.0, +-210,-370,0.0,78.0, +-210,-350,0.0,78.0, +-210,-330,0.0,78.0, +-210,-310,0.0,78.0, +-210,-290,0.0,78.0, +-210,-270,0.0,78.0, +-210,-250,0.0,78.0, +-210,-230,0.0,78.0, +-210,-210,0.0,78.0, +-210,-190,0.0,78.0, +-210,-170,0.0,78.0, +-210,-150,0.0,78.0, +-210,-130,0.0,78.0, +-210,-110,0.0,78.0, +-210,-90,0.0,78.0, +-210,-70,0.0,78.0, +-210,-50,0.0,78.0, +-210,-30,0.0,78.0, +-210,-10,0.0,78.0, +-210,10,0.0,78.0, +-210,30,0.0,78.0, +-210,50,0.0,78.0, +-210,70,0.0,78.0, +-210,90,0.0,78.0, +-210,110,0.0,78.0, +-210,130,0.0,78.0, +-210,150,0.0,78.0, +-210,170,0.0,78.0, +-210,190,0.0,78.0, +-210,210,0.0,78.0, +-210,230,0.0,78.0, +-210,250,0.0,78.0, +-210,270,0.0,78.0, +-210,290,0.0,78.0, +-210,310,0.0,78.0, +-210,330,0.0,78.0, +-210,350,0.0,78.0, +-210,370,0.0,78.0, +-210,390,0.0,78.0, +210,-390,0.0,,16.0 +210,-370,0.0,,16.0 +210,-350,0.0,,16.0 +210,-330,0.0,,16.0 +210,-310,0.0,,16.0 +210,-290,0.0,,16.0 +210,-270,0.0,,16.0 +210,-250,0.0,,16.0 +210,-230,0.0,,16.0 +210,-210,0.0,,16.0 +210,-190,0.0,,16.0 +210,-170,0.0,,16.0 +210,-150,0.0,,16.0 +210,-130,0.0,,16.0 +210,-110,0.0,,16.0 +210,-90,0.0,,16.0 +210,-70,0.0,,16.0 +210,-50,0.0,,16.0 +210,-30,0.0,,16.0 +210,-10,0.0,,16.0 +210,10,0.0,,16.0 +210,30,0.0,,16.0 +210,50,0.0,,16.0 +210,70,0.0,,16.0 +210,90,0.0,,16.0 +210,110,0.0,,16.0 +210,130,0.0,,16.0 +210,150,0.0,,16.0 +210,170,0.0,,16.0 +210,190,0.0,,16.0 +210,210,0.0,,16.0 +210,230,0.0,,16.0 +210,250,0.0,,16.0 +210,270,0.0,,16.0 +210,290,0.0,,16.0 +210,310,0.0,,16.0 +210,330,0.0,,16.0 +210,350,0.0,,16.0 +210,370,0.0,,16.0 +210,390,0.0,,16.0 \ No newline at end of file diff --git a/sample_projects/dirichlet_from_file/custom_modules/custom.cpp b/sample_projects/dirichlet_from_file/custom_modules/custom.cpp new file mode 100644 index 000000000..59b23bd93 --- /dev/null +++ b/sample_projects/dirichlet_from_file/custom_modules/custom.cpp @@ -0,0 +1,214 @@ +/* +############################################################################### +# If you use PhysiCell in your project, please cite PhysiCell and the version # +# number, such as below: # +# # +# We implemented and solved the model using PhysiCell (Version x.y.z) [1]. # +# # +# [1] A Ghaffarizadeh, R Heiland, SH Friedman, SM Mumenthaler, and P Macklin, # +# PhysiCell: an Open Source Physics-Based Cell Simulator for Multicellu- # +# lar Systems, PLoS Comput. Biol. 14(2): e1005991, 2018 # +# DOI: 10.1371/journal.pcbi.1005991 # +# # +# See VERSION.txt or call get_PhysiCell_version() to get the current version # +# x.y.z. Call display_citations() to get detailed information on all cite-# +# able software used in your PhysiCell application. # +# # +# Because PhysiCell extensively uses BioFVM, we suggest you also cite BioFVM # +# as below: # +# # +# We implemented and solved the model using PhysiCell (Version x.y.z) [1], # +# with BioFVM [2] to solve the transport equations. # +# # +# [1] A Ghaffarizadeh, R Heiland, SH Friedman, SM Mumenthaler, and P Macklin, # +# PhysiCell: an Open Source Physics-Based Cell Simulator for Multicellu- # +# lar Systems, PLoS Comput. Biol. 14(2): e1005991, 2018 # +# DOI: 10.1371/journal.pcbi.1005991 # +# # +# [2] A Ghaffarizadeh, SH Friedman, and P Macklin, BioFVM: an efficient para- # +# llelized diffusive transport solver for 3-D biological simulations, # +# Bioinformatics 32(8): 1256-8, 2016. DOI: 10.1093/bioinformatics/btv730 # +# # +############################################################################### +# # +# BSD 3-Clause License (see https://opensource.org/licenses/BSD-3-Clause) # +# # +# Copyright (c) 2015-2021, Paul Macklin and the PhysiCell Project # +# All rights reserved. # +# # +# Redistribution and use in source and binary forms, with or without # +# modification, are permitted provided that the following conditions are met: # +# # +# 1. Redistributions of source code must retain the above copyright notice, # +# this list of conditions and the following disclaimer. # +# # +# 2. Redistributions in binary form must reproduce the above copyright # +# notice, this list of conditions and the following disclaimer in the # +# documentation and/or other materials provided with the distribution. # +# # +# 3. Neither the name of the copyright holder nor the names of its # +# contributors may be used to endorse or promote products derived from this # +# software without specific prior written permission. # +# # +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # +# POSSIBILITY OF SUCH DAMAGE. # +# # +############################################################################### +*/ + +#include "./custom.h" + +void create_cell_types( void ) +{ + // set the random seed + if (parameters.ints.find_index("random_seed") != -1) + { + SeedRandom(parameters.ints("random_seed")); + } + + /* + Put any modifications to default cell definition here if you + want to have "inherited" by other cell types. + + This is a good place to set default functions. + */ + + initialize_default_cell_definition(); + cell_defaults.phenotype.secretion.sync_to_microenvironment( µenvironment ); + + cell_defaults.functions.volume_update_function = standard_volume_update_function; + cell_defaults.functions.update_velocity = standard_update_cell_velocity; + + cell_defaults.functions.update_migration_bias = NULL; + cell_defaults.functions.update_phenotype = NULL; // update_cell_and_death_parameters_O2_based; + cell_defaults.functions.custom_cell_rule = NULL; + cell_defaults.functions.contact_function = NULL; + + cell_defaults.functions.add_cell_basement_membrane_interactions = NULL; + cell_defaults.functions.calculate_distance_to_membrane = NULL; + + /* + This parses the cell definitions in the XML config file. + */ + + initialize_cell_definitions_from_pugixml(); + + /* + This builds the map of cell definitions and summarizes the setup. + */ + + build_cell_definitions_maps(); + + /* + This intializes cell signal and response dictionaries + */ + + setup_signal_behavior_dictionaries(); + + /* + Cell rule definitions + */ + + setup_cell_rules(); + + /* + Put any modifications to individual cell definitions here. + + This is a good place to set custom functions. + */ + + cell_defaults.functions.update_phenotype = phenotype_function; + cell_defaults.functions.custom_cell_rule = custom_function; + cell_defaults.functions.contact_function = contact_function; + + /* + This builds the map of cell definitions and summarizes the setup. + */ + + display_cell_definitions( std::cout ); + + return; +} + +void setup_microenvironment( void ) +{ + // set domain parameters + + // put any custom code to set non-homogeneous initial conditions or + // extra Dirichlet nodes here. + + // initialize BioFVM + + initialize_microenvironment(); + + return; +} + +void setup_tissue( void ) +{ + double Xmin = microenvironment.mesh.bounding_box[0]; + double Ymin = microenvironment.mesh.bounding_box[1]; + double Zmin = microenvironment.mesh.bounding_box[2]; + + double Xmax = microenvironment.mesh.bounding_box[3]; + double Ymax = microenvironment.mesh.bounding_box[4]; + double Zmax = microenvironment.mesh.bounding_box[5]; + + if( default_microenvironment_options.simulate_2D == true ) + { + Zmin = 0.0; + Zmax = 0.0; + } + + double Xrange = Xmax - Xmin; + double Yrange = Ymax - Ymin; + double Zrange = Zmax - Zmin; + + // create some of each type of cell + + Cell* pC; + + for( int k=0; k < cell_definitions_by_index.size() ; k++ ) + { + Cell_Definition* pCD = cell_definitions_by_index[k]; + std::cout << "Placing cells of type " << pCD->name << " ... " << std::endl; + for( int n = 0 ; n < parameters.ints("number_of_cells") ; n++ ) + { + std::vector position = {0,0,0}; + position[0] = Xmin + UniformRandom()*Xrange; + position[1] = Ymin + UniformRandom()*Yrange; + position[2] = Zmin + UniformRandom()*Zrange; + + pC = create_cell( *pCD ); + pC->assign_position( position ); + } + } + std::cout << std::endl; + + // load cells from your CSV file (if enabled) + load_cells_from_pugixml(); + set_parameters_from_distributions(); + + return; +} + +std::vector my_coloring_function( Cell* pCell ) +{ return paint_by_number_cell_coloring(pCell); } + +void phenotype_function( Cell* pCell, Phenotype& phenotype, double dt ) +{ return; } + +void custom_function( Cell* pCell, Phenotype& phenotype , double dt ) +{ return; } + +void contact_function( Cell* pMe, Phenotype& phenoMe , Cell* pOther, Phenotype& phenoOther , double dt ) +{ return; } \ No newline at end of file diff --git a/sample_projects/dirichlet_from_file/custom_modules/custom.h b/sample_projects/dirichlet_from_file/custom_modules/custom.h new file mode 100644 index 000000000..0e6df8d02 --- /dev/null +++ b/sample_projects/dirichlet_from_file/custom_modules/custom.h @@ -0,0 +1,92 @@ +/* +############################################################################### +# If you use PhysiCell in your project, please cite PhysiCell and the version # +# number, such as below: # +# # +# We implemented and solved the model using PhysiCell (Version x.y.z) [1]. # +# # +# [1] A Ghaffarizadeh, R Heiland, SH Friedman, SM Mumenthaler, and P Macklin, # +# PhysiCell: an Open Source Physics-Based Cell Simulator for Multicellu- # +# lar Systems, PLoS Comput. Biol. 14(2): e1005991, 2018 # +# DOI: 10.1371/journal.pcbi.1005991 # +# # +# See VERSION.txt or call get_PhysiCell_version() to get the current version # +# x.y.z. Call display_citations() to get detailed information on all cite-# +# able software used in your PhysiCell application. # +# # +# Because PhysiCell extensively uses BioFVM, we suggest you also cite BioFVM # +# as below: # +# # +# We implemented and solved the model using PhysiCell (Version x.y.z) [1], # +# with BioFVM [2] to solve the transport equations. # +# # +# [1] A Ghaffarizadeh, R Heiland, SH Friedman, SM Mumenthaler, and P Macklin, # +# PhysiCell: an Open Source Physics-Based Cell Simulator for Multicellu- # +# lar Systems, PLoS Comput. Biol. 14(2): e1005991, 2018 # +# DOI: 10.1371/journal.pcbi.1005991 # +# # +# [2] A Ghaffarizadeh, SH Friedman, and P Macklin, BioFVM: an efficient para- # +# llelized diffusive transport solver for 3-D biological simulations, # +# Bioinformatics 32(8): 1256-8, 2016. DOI: 10.1093/bioinformatics/btv730 # +# # +############################################################################### +# # +# BSD 3-Clause License (see https://opensource.org/licenses/BSD-3-Clause) # +# # +# Copyright (c) 2015-2021, Paul Macklin and the PhysiCell Project # +# All rights reserved. # +# # +# Redistribution and use in source and binary forms, with or without # +# modification, are permitted provided that the following conditions are met: # +# # +# 1. Redistributions of source code must retain the above copyright notice, # +# this list of conditions and the following disclaimer. # +# # +# 2. Redistributions in binary form must reproduce the above copyright # +# notice, this list of conditions and the following disclaimer in the # +# documentation and/or other materials provided with the distribution. # +# # +# 3. Neither the name of the copyright holder nor the names of its # +# contributors may be used to endorse or promote products derived from this # +# software without specific prior written permission. # +# # +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # +# POSSIBILITY OF SUCH DAMAGE. # +# # +############################################################################### +*/ + +#include "../core/PhysiCell.h" +#include "../modules/PhysiCell_standard_modules.h" + +using namespace BioFVM; +using namespace PhysiCell; + +// setup functions to help us along + +void create_cell_types( void ); +void setup_tissue( void ); + +// set up the BioFVM microenvironment +void setup_microenvironment( void ); + +// custom pathology coloring function + +std::vector my_coloring_function( Cell* ); + +// custom functions can go here + +void phenotype_function( Cell* pCell, Phenotype& phenotype, double dt ); +void custom_function( Cell* pCell, Phenotype& phenotype , double dt ); + +void contact_function( Cell* pMe, Phenotype& phenoMe , Cell* pOther, Phenotype& phenoOther , double dt ); + diff --git a/sample_projects/dirichlet_from_file/main.cpp b/sample_projects/dirichlet_from_file/main.cpp new file mode 100644 index 000000000..bf6e65d15 --- /dev/null +++ b/sample_projects/dirichlet_from_file/main.cpp @@ -0,0 +1,254 @@ +/* +############################################################################### +# If you use PhysiCell in your project, please cite PhysiCell and the version # +# number, such as below: # +# # +# We implemented and solved the model using PhysiCell (Version x.y.z) [1]. # +# # +# [1] A Ghaffarizadeh, R Heiland, SH Friedman, SM Mumenthaler, and P Macklin, # +# PhysiCell: an Open Source Physics-Based Cell Simulator for Multicellu- # +# lar Systems, PLoS Comput. Biol. 14(2): e1005991, 2018 # +# DOI: 10.1371/journal.pcbi.1005991 # +# # +# See VERSION.txt or call get_PhysiCell_version() to get the current version # +# x.y.z. Call display_citations() to get detailed information on all cite-# +# able software used in your PhysiCell application. # +# # +# Because PhysiCell extensively uses BioFVM, we suggest you also cite BioFVM # +# as below: # +# # +# We implemented and solved the model using PhysiCell (Version x.y.z) [1], # +# with BioFVM [2] to solve the transport equations. # +# # +# [1] A Ghaffarizadeh, R Heiland, SH Friedman, SM Mumenthaler, and P Macklin, # +# PhysiCell: an Open Source Physics-Based Cell Simulator for Multicellu- # +# lar Systems, PLoS Comput. Biol. 14(2): e1005991, 2018 # +# DOI: 10.1371/journal.pcbi.1005991 # +# # +# [2] A Ghaffarizadeh, SH Friedman, and P Macklin, BioFVM: an efficient para- # +# llelized diffusive transport solver for 3-D biological simulations, # +# Bioinformatics 32(8): 1256-8, 2016. DOI: 10.1093/bioinformatics/btv730 # +# # +############################################################################### +# # +# BSD 3-Clause License (see https://opensource.org/licenses/BSD-3-Clause) # +# # +# Copyright (c) 2015-2022, Paul Macklin and the PhysiCell Project # +# All rights reserved. # +# # +# Redistribution and use in source and binary forms, with or without # +# modification, are permitted provided that the following conditions are met: # +# # +# 1. Redistributions of source code must retain the above copyright notice, # +# this list of conditions and the following disclaimer. # +# # +# 2. Redistributions in binary form must reproduce the above copyright # +# notice, this list of conditions and the following disclaimer in the # +# documentation and/or other materials provided with the distribution. # +# # +# 3. Neither the name of the copyright holder nor the names of its # +# contributors may be used to endorse or promote products derived from this # +# software without specific prior written permission. # +# # +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # +# POSSIBILITY OF SUCH DAMAGE. # +# # +############################################################################### +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "./core/PhysiCell.h" +#include "./modules/PhysiCell_standard_modules.h" + +// put custom code modules here! + +#include "./custom_modules/custom.h" + +using namespace BioFVM; +using namespace PhysiCell; + +int main( int argc, char* argv[] ) +{ + // load and parse settings file(s) + + bool XML_status = false; + char copy_command [1024]; + if( argc > 1 ) + { + XML_status = load_PhysiCell_config_file( argv[1] ); + sprintf( copy_command , "cp %s %s" , argv[1] , PhysiCell_settings.folder.c_str() ); + } + else + { + XML_status = load_PhysiCell_config_file( "./config/PhysiCell_settings.xml" ); + sprintf( copy_command , "cp ./config/PhysiCell_settings.xml %s" , PhysiCell_settings.folder.c_str() ); + } + if( !XML_status ) + { exit(-1); } + + // copy config file to output directry + system( copy_command ); + + // OpenMP setup + omp_set_num_threads(PhysiCell_settings.omp_num_threads); + + // time setup + std::string time_units = "min"; + + /* Microenvironment setup */ + + setup_microenvironment(); // modify this in the custom code + + /* PhysiCell setup */ + + // set mechanics voxel size, and match the data structure to BioFVM + double mechanics_voxel_size = 30; + Cell_Container* cell_container = create_cell_container_for_microenvironment( microenvironment, mechanics_voxel_size ); + + /* Users typically start modifying here. START USERMODS */ + + create_cell_types(); + + setup_tissue(); + + /* Users typically stop modifying here. END USERMODS */ + + // set MultiCellDS save options + + set_save_biofvm_mesh_as_matlab( true ); + set_save_biofvm_data_as_matlab( true ); + set_save_biofvm_cell_data( true ); + set_save_biofvm_cell_data_as_custom_matlab( true ); + + // save a simulation snapshot + + char filename[1024]; + sprintf( filename , "%s/initial" , PhysiCell_settings.folder.c_str() ); + save_PhysiCell_to_MultiCellDS_v2( filename , microenvironment , PhysiCell_globals.current_time ); + + // save a quick SVG cross section through z = 0, after setting its + // length bar to 200 microns + + PhysiCell_SVG_options.length_bar = 200; + + // for simplicity, set a pathology coloring function + + std::vector (*cell_coloring_function)(Cell*) = my_coloring_function; + std::string (*substrate_coloring_function)(double, double, double) = paint_by_density_percentage; + + sprintf( filename , "%s/initial.svg" , PhysiCell_settings.folder.c_str() ); + SVG_plot( filename , microenvironment, 0.0 , PhysiCell_globals.current_time, cell_coloring_function, substrate_coloring_function ); + + sprintf( filename , "%s/legend.svg" , PhysiCell_settings.folder.c_str() ); + create_plot_legend( filename , cell_coloring_function ); + + display_citations(); + + // set the performance timers + + BioFVM::RUNTIME_TIC(); + BioFVM::TIC(); + + std::ofstream report_file; + if( PhysiCell_settings.enable_legacy_saves == true ) + { + sprintf( filename , "%s/simulation_report.txt" , PhysiCell_settings.folder.c_str() ); + + report_file.open(filename); // create the data log file + report_file<<"simulated time\tnum cells\tnum division\tnum death\twall time"< PhysiCell_globals.next_full_save_time - 0.5 * diffusion_dt ) + { + display_simulation_status( std::cout ); + if( PhysiCell_settings.enable_legacy_saves == true ) + { + log_output( PhysiCell_globals.current_time , PhysiCell_globals.full_output_index, microenvironment, report_file); + } + + if( PhysiCell_settings.enable_full_saves == true ) + { + sprintf( filename , "%s/output%08u" , PhysiCell_settings.folder.c_str(), PhysiCell_globals.full_output_index ); + + save_PhysiCell_to_MultiCellDS_v2( filename , microenvironment , PhysiCell_globals.current_time ); + } + + PhysiCell_globals.full_output_index++; + PhysiCell_globals.next_full_save_time += PhysiCell_settings.full_save_interval; + } + + // save SVG plot if it's time + if( PhysiCell_globals.current_time > PhysiCell_globals.next_SVG_save_time - 0.5 * diffusion_dt ) + { + if( PhysiCell_settings.enable_SVG_saves == true ) + { + sprintf( filename , "%s/snapshot%08u.svg" , PhysiCell_settings.folder.c_str() , PhysiCell_globals.SVG_output_index ); + SVG_plot( filename , microenvironment, 0.0 , PhysiCell_globals.current_time, cell_coloring_function, substrate_coloring_function); + + PhysiCell_globals.SVG_output_index++; + PhysiCell_globals.next_SVG_save_time += PhysiCell_settings.SVG_save_interval; + } + } + + // update the microenvironment + microenvironment.simulate_diffusion_decay( diffusion_dt ); + + // run PhysiCell + ((Cell_Container *)microenvironment.agent_container)->update_all_cells( PhysiCell_globals.current_time ); + + /* + Custom add-ons could potentially go here. + */ + + PhysiCell_globals.current_time += diffusion_dt; + } + + if( PhysiCell_settings.enable_legacy_saves == true ) + { + log_output(PhysiCell_globals.current_time, PhysiCell_globals.full_output_index, microenvironment, report_file); + report_file.close(); + } + } + catch( const std::exception& e ) + { // reference to the base of a polymorphic object + std::cout << e.what(); // information from length_error printed + } + + // save a final simulation snapshot + + sprintf( filename , "%s/final" , PhysiCell_settings.folder.c_str() ); + save_PhysiCell_to_MultiCellDS_v2( filename , microenvironment , PhysiCell_globals.current_time ); + + sprintf( filename , "%s/final.svg" , PhysiCell_settings.folder.c_str() ); + SVG_plot(filename, microenvironment, 0.0, PhysiCell_globals.current_time, cell_coloring_function, substrate_coloring_function); + + // timer + + std::cout << std::endl << "Total simulation runtime: " << std::endl; + BioFVM::display_stopwatch_value( std::cout , BioFVM::runtime_stopwatch_value() ); + + return 0; +} From 8f96226d62b0ba26158e398f44d567f86f89f940 Mon Sep 17 00:00:00 2001 From: Daniel Bergman Date: Thu, 22 May 2025 08:53:07 -0400 Subject: [PATCH 2/4] - fix logic reading in substrates.csv without a header --- BioFVM/BioFVM_microenvironment.cpp | 37 ++++++++++++++++-------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/BioFVM/BioFVM_microenvironment.cpp b/BioFVM/BioFVM_microenvironment.cpp index 17353d671..912f0d135 100644 --- a/BioFVM/BioFVM_microenvironment.cpp +++ b/BioFVM/BioFVM_microenvironment.cpp @@ -1309,6 +1309,7 @@ void load_initial_conditions_from_csv(std::string filename) char c = line.c_str()[0]; std::vector substrate_indices; bool header_provided = false; + std::vector voxel_set = {}; // set to check that no voxel value is set twice if( c == 'X' || c == 'x' ) { // do not support this with a header yet @@ -1343,21 +1344,18 @@ void load_initial_conditions_from_csv(std::string filename) { std::stringstream stream(line); std::string field; - int i = 0; + int i = -1; while (std::getline(stream, field, ',')) { + i++; if (i<3) {continue;} // skip (x,y,z) substrate_indices.push_back(i-3); // the substrate index is the column index - 3 (since x,y,z are the first 3 columns) - i++; } - // in this case, we want to read this first line, so close the file and re-open so that we start with this line - file.close(); - std::ifstream file(filename, std::ios::in); - std::getline(file, line); + // in this case, we want to read this first line, so read it now and the below logic will loop through the remaining lines + get_row_from_substrate_initial_condition_csv(voxel_set, line, substrate_indices, header_provided); } std::cout << "Loading substrate initial conditions from CSV file " << filename << " ... " << std::endl; - std::vector voxel_set = {}; // set to check that no voxel value is set twice while (std::getline(file, line)) { @@ -1615,7 +1613,7 @@ void set_dirichlet_boundaries_from_file( void ) void load_dirichlet_conditions_from_matlab(std::string filename) { - std::cerr << "ERROR: Load BioFVM dirichlet conditions from MATLAB not yet supported." << std::endl; + std::cerr << "ERROR: Load BioFVM dirichlet conditions from MATLAB not yet supported. Use a CSV file instead." << std::endl; exit(-1); } @@ -1644,6 +1642,7 @@ void load_dirichlet_conditions_from_csv(std::string filename) std::vector substrate_indices; bool header_provided = false; int n_cols; + std::vector voxel_set = {}; // set to check that no voxel value is set twice if( c == 'X' || c == 'x' ) { // do not support this with a header yet @@ -1679,23 +1678,27 @@ void load_dirichlet_conditions_from_csv(std::string filename) { std::stringstream stream(line); std::string field; - int i = 0; + int i = -1; while (std::getline(stream, field, ',')) { + i++; if (i<3) {continue;} // skip (x,y,z) substrate_indices.push_back(i-3); // the substrate index is the column index - 3 (since x,y,z are the first 3 columns) + } + // If the line ends with a comma, that implies a final empty field so no DC set at that voxel-substrate pair + if (!line.empty() && line.back() == ',') + { i++; + if (i >= 3) + { + substrate_indices.push_back(i - 3); + } } - // in this case, we want to read this first line, so close the file and re-open so that we start with this line n_cols = 3 + substrate_indices.size(); - file.close(); - std::ifstream file(filename, std::ios::in); - std::getline(file, line); + // in this case, we want to read this first line, so read it now and the below logic will loop through the remaining lines + get_row_from_dirichlet_condition_csv(voxel_set, line, substrate_indices, header_provided, n_cols); } - - std::cout << "Loading substrate dirichlet conditions from CSV file " << filename << " ... " << std::endl; - std::vector voxel_set = {}; // set to check that no voxel value is set twice - + while (std::getline(file, line)) { get_row_from_dirichlet_condition_csv(voxel_set, line, substrate_indices, header_provided, n_cols); From 2ff66d62d8be8360795bf24535a2c8ffe51550dc Mon Sep 17 00:00:00 2001 From: Daniel Bergman Date: Fri, 21 Aug 2026 19:39:35 -0400 Subject: [PATCH 3/4] Restore trim_cr on substrate csv rows and harden dirichlet csv parsing (#70) Merging development into feature-dc-init-file dropped both trim_cr calls from load_initial_conditions_from_csv. The two functions are near-identical copies, so the resolution landed development's additions on the dirichlet copy and kept the feature branch's untouched version of the substrate copy. A CRLF substrates.csv with a header therefore fails again with "Substrate not found in the BioFVM microenvironment", since the final column name carries the trailing carriage return. Both csv row processors also indexed their parsed row without consulting its length, which is out of bounds for a malformed row: - a blank or whitespace-only line leaves csv_to_vector's output empty, so data[0..2] reads past the end (and the churn loop itself walks past the terminator looking for a digit). - substrate_indices is sized from the header, so a short row runs data[ci + 3] off the end. - dirichlet_csv_to_vector writes is_missing[ind] and data[ind] once per comma with no bound, so a row with more fields than the header established writes past the end of both vectors. Blank lines are now skipped in both, matching load_cells_csv_v1 and process_csv_v2_line, and a row whose column count disagrees with the header is a hard error rather than a silent partial write. Ordering the substrate guard ahead of the existing warning also stops data.size() - 3 from underflowing on a short row. dirichlet_csv_to_vector additionally treats a whitespace-only field as omitted rather than feeding it to std::stod, and reports a field that is not a complete number instead of letting std::stod throw uncaught or silently accepting "1.5abc" as 1.5. Co-authored-by: Claude Opus 5 --- BioFVM/BioFVM_microenvironment.cpp | 21 ++++++++++ BioFVM/BioFVM_vector.cpp | 65 +++++++++++++++++++++--------- 2 files changed, 68 insertions(+), 18 deletions(-) diff --git a/BioFVM/BioFVM_microenvironment.cpp b/BioFVM/BioFVM_microenvironment.cpp index 7c30654f7..10de3e564 100644 --- a/BioFVM/BioFVM_microenvironment.cpp +++ b/BioFVM/BioFVM_microenvironment.cpp @@ -1307,6 +1307,7 @@ void load_initial_conditions_from_csv(std::string filename) // determine if header row exists std::string line; std::getline( file , line ); + trim_cr(line); char c = line.c_str()[0]; std::vector substrate_indices; bool header_provided = false; @@ -1360,6 +1361,7 @@ void load_initial_conditions_from_csv(std::string filename) while (std::getline(file, line)) { + trim_cr(line); get_row_from_substrate_initial_condition_csv(voxel_set, line, substrate_indices, header_provided); } @@ -1380,9 +1382,24 @@ void load_initial_conditions_from_csv(std::string filename) void get_row_from_substrate_initial_condition_csv(std::vector &voxel_set, const std::string line, const std::vector substrate_indices, const bool header_provided) { static bool warning_issued = false; + + if (line.find_first_not_of(" \t\r") == std::string::npos) + { return; } // skip blank lines + std::vector data; csv_to_vector(line.c_str(), data); + // holding a row to the expected column count is also what keeps every data[...] access below in bounds + if (data.size() != substrate_indices.size() + 3) + { + std::cout << "ERROR : A row of the .csv file specifying BioFVM initial conditions has the wrong number of values." << std::endl + << "\tExpected: " << substrate_indices.size() + 3 << " (x, y, z and " << substrate_indices.size() << " substrate value(s))" << std::endl + << "\tFound: " << data.size() << std::endl + << "\tNote that an empty field is not a value here; every column must hold a number." << std::endl + << "\tOffending row: " << line << std::endl; + exit(-1); + } + if (!(warning_issued) && !(header_provided) && (data.size() != (microenvironment.number_of_densities() + 3))) { std::cout << "WARNING: Wrong number of density values supplied in the .csv file specifying BioFVM initial conditions." << std::endl @@ -1715,6 +1732,10 @@ void load_dirichlet_conditions_from_csv(std::string filename) void get_row_from_dirichlet_condition_csv(std::vector &voxel_set, const std::string line, const std::vector substrate_indices, const bool header_provided, int n_cols) { static bool warning_issued = false; + + if (line.find_first_not_of(" \t\r") == std::string::npos) + { return; } // skip blank lines + std::vector is_missing; std::vector data; is_missing.resize(n_cols); diff --git a/BioFVM/BioFVM_vector.cpp b/BioFVM/BioFVM_vector.cpp index 6981fef9d..eab8bc91f 100644 --- a/BioFVM/BioFVM_vector.cpp +++ b/BioFVM/BioFVM_vector.cpp @@ -47,6 +47,8 @@ */ #include "BioFVM_vector.h" +#include +#include /* some global BioFVM strings */ @@ -376,6 +378,49 @@ void csv_to_vector( const char* buffer , std::vector& vect ) return; } +// store one field of a dirichlet-condition csv row. The bounds check matters: a row carrying more +// fields than the header established would otherwise write past the end of both vectors. +static void store_dirichlet_csv_entry( const char* buffer , const std::string& entry , std::vector& is_missing , std::vector& data , const size_t ind ) +{ + if (ind >= is_missing.size()) + { + std::cerr << "Error: Too many data supplied in a row of the .csv file specifying BioFVM dirichlet conditions." << std::endl; + std::cerr << "\tExpected: " << is_missing.size() << ". Found: " << ind + 1 << " or more." << std::endl; + std::cerr << "\tRow: " << buffer << std::endl; + exit(-1); + } + + // a field holding nothing but whitespace is omitted, so that " , " reads the same as "," + size_t first = entry.find_first_not_of(" \t\r"); + if (first == std::string::npos) + { + is_missing[ind] = true; + return; + } + std::string trimmed = entry.substr(first, entry.find_last_not_of(" \t\r") - first + 1); + + double value = 0.0; + size_t parsed = 0; + try + { + value = std::stod(trimmed, &parsed); + } + catch (const std::logic_error&) // std::stod throws invalid_argument on no conversion, out_of_range on overflow + { + parsed = 0; + } + if (parsed != trimmed.size()) // no conversion, or trailing garbage as in "1.5abc" + { + std::cerr << "Error: Column " << ind + 1 << " of a row of the .csv file specifying BioFVM dirichlet conditions is not a number." << std::endl; + std::cerr << "\tOffending value: " << trimmed << std::endl; + std::cerr << "\tRow: " << buffer << std::endl; + exit(-1); + } + + data[ind] = value; + is_missing[ind] = false; +} + void dirichlet_csv_to_vector( const char* buffer , std::vector& is_missing , std::vector& data ) { size_t ind = 0; @@ -385,15 +430,7 @@ void dirichlet_csv_to_vector( const char* buffer , std::vector& is_missing { if(buffer[i] == ',') { - if(entry.empty()) - { - is_missing[ind] = true; - } - else - { - data[ind] = std::stod(entry); - is_missing[ind] = false; - } + store_dirichlet_csv_entry(buffer, entry, is_missing, data, ind); entry.clear(); ind++; } @@ -404,15 +441,7 @@ void dirichlet_csv_to_vector( const char* buffer , std::vector& is_missing i++; } // Handle the last entry - if(entry.empty()) - { - is_missing[ind] = true; - } - else - { - data[ind] = std::stod(entry); - is_missing[ind] = false; - } + store_dirichlet_csv_entry(buffer, entry, is_missing, data, ind); if (is_missing[0] || is_missing[1] || is_missing[2]) { From 9af148b4151878cd6f563c8fc7bafc6797423b70 Mon Sep 17 00:00:00 2001 From: Daniel Bergman Date: Fri, 21 Aug 2026 19:44:50 -0400 Subject: [PATCH 4/4] fix error messages for wrong number of densities --- BioFVM/BioFVM_microenvironment.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BioFVM/BioFVM_microenvironment.cpp b/BioFVM/BioFVM_microenvironment.cpp index 10de3e564..04f2ee5a6 100644 --- a/BioFVM/BioFVM_microenvironment.cpp +++ b/BioFVM/BioFVM_microenvironment.cpp @@ -1403,7 +1403,7 @@ void get_row_from_substrate_initial_condition_csv(std::vector &voxel_set, c if (!(warning_issued) && !(header_provided) && (data.size() != (microenvironment.number_of_densities() + 3))) { std::cout << "WARNING: Wrong number of density values supplied in the .csv file specifying BioFVM initial conditions." << std::endl - << "\tExpected: " << microenvironment.number_of_voxels() << std::endl + << "\tExpected: " << microenvironment.number_of_densities() << std::endl << "\tFound: " << data.size() - 3 << std::endl << "\tRemember, save your csv with columns as: x, y, z, substrate_0, substrate_1,...." << std::endl << "\tThis could also be resolved by including a header row \"x,y,z,[substrate_i0,substrate_i1]\"" << std::endl; @@ -1746,7 +1746,7 @@ void get_row_from_dirichlet_condition_csv(std::vector &voxel_set, const std if (!(warning_issued) && !(header_provided) && (data.size() != (microenvironment.number_of_densities() + 3))) { std::cout << "WARNING: Wrong number of density data supplied in the .csv file specifying BioFVM dirichlet conditions." << std::endl - << "\tExpected: " << microenvironment.number_of_voxels() << std::endl + << "\tExpected: " << microenvironment.number_of_densities() << std::endl << "\tFound: " << data.size() - 3 << std::endl << "\tRemember, save your csv with columns as: x, y, z, substrate_0, substrate_1,...." << std::endl << "\tThis could also be resolved by including a header row \"x,y,z,[substrate_i0,substrate_i1]\"" << std::endl;