From b88d3684aa3f03610cd1cee0d83e9c3ba9f5a511 Mon Sep 17 00:00:00 2001 From: Randy Heiland Date: Fri, 12 Dec 2025 07:21:03 -0500 Subject: [PATCH 1/5] update --- unit_tests/resume_sim/README.md | 32 ++++ unit_tests/resume_sim/main.cpp | 290 ++++++++++++++++++++++++++++++++ 2 files changed, 322 insertions(+) create mode 100644 unit_tests/resume_sim/README.md create mode 100644 unit_tests/resume_sim/main.cpp diff --git a/unit_tests/resume_sim/README.md b/unit_tests/resume_sim/README.md new file mode 100644 index 000000000..c775a6568 --- /dev/null +++ b/unit_tests/resume_sim/README.md @@ -0,0 +1,32 @@ +## Resume a simulation from a previous checkpoint + +* provide a MultiCellDS file (output000*N.xml) as a starting point to continue a simulation +* does not handle intracellular model state +* does not currently recover "output*_graph.txt" data; rather relies on the next mechanics step to recreate the information +* if a model relies on global data generated in custom C++ code that is not captured in the MultiCellDS data, it cannot be recovered + +Normal simulation: + +``` +project config/cycle_phase_3cells_custom_vecs.xml # writes output to /output +``` + + +Resume a simulation: + +``` +Usage options: +- no args: will try to use ./config/PhysiCell_settings.xml +- 1 arg: will try to use it as the config file +- 5 args to resume a simulation: + <.xml config file> + + e.g. project config/PhysiCell_settings.xml output output00000042.xml 43 43 + +``` + +``` +project config/cycle_phase_3cells_custom_vecs.xml output output00000048.xml 49 49 +``` + + diff --git a/unit_tests/resume_sim/main.cpp b/unit_tests/resume_sim/main.cpp new file mode 100644 index 000000000..510d72212 --- /dev/null +++ b/unit_tests/resume_sim/main.cpp @@ -0,0 +1,290 @@ +/* +############################################################################### +# 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-2023, 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]; + std::string resume_from_dir; + std::string resume_from_file; + int next_full_index, next_svg_index; + + // std::cout << "argc = " << argc << "argv=" << argv << std::endl; + if( argc < 5 ) + { + std::cout << "\n Too few arguments. Usage: <.xml config file> " << std::endl; + std::cout << " e.g. config/PhysiCell_settings.xml output output00000042.xml" << std::endl; + exit(-1); + + XML_status = load_PhysiCell_config_file( argv[1] ); + if( !XML_status ) + { exit(-1); } + + sprintf( copy_command , "cp %s %s" , argv[1] , PhysiCell_settings.folder.c_str() ); + // copy config file to output directry + system( copy_command ); + + resume_from_dir = argv[2]; + resume_from_file = argv[3]; + + next_full_index = std::stoi(argv[4]); + next_svg_index = std::stoi(argv[5]); + } + + // 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(); + + + //--------- + bool resume_from_checkpoint = true; + if ( !resume_from_checkpoint ) + { + setup_tissue(); + } + else + { + // read in and resume from a checkpoint file (replaces setup_tissue) + + // This sets PhysiCell_globals.current_time to be that of resumed checkpoint + // resume_from_MultiCellDS_xml("output_test_resume_phases_custom_vec", "output00000072.xml"); + + // int resume_from_MultiCellDS(std::string folder_path, std::string xml_filename, bool create_cells = true, bool debug_print = false); + int retval = resume_from_MultiCellDS(resume_from_dir, resume_from_file); + + // Another option: don't recreate state, but print out state of cells + // int retval = resume_from_MultiCellDS(resume_from_dir, resume_from_file, false, true); + if (retval < 0) + { + std::cout << "Error in resume_from_MultiCellDS, exiting" << std::endl; + exit(-1); + } + + // Need to adjust these values + PhysiCell_globals.next_full_save_time = PhysiCell_globals.current_time + PhysiCell_settings.full_save_interval; + PhysiCell_globals.next_SVG_save_time = PhysiCell_globals.current_time + PhysiCell_settings.SVG_save_interval; + + PhysiCell_globals.full_output_index = next_full_index; + PhysiCell_globals.SVG_output_index = next_svg_index; + } + + /* 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 ); + + 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 ); + + 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 ); + + std::cout << std::endl << "Total simulation runtime: " << std::endl; + BioFVM::display_stopwatch_value( std::cout , BioFVM::runtime_stopwatch_value() ); + + return 0; +} From 068080fde2249ebb64244f3f8ba4c2d3849664fc Mon Sep 17 00:00:00 2001 From: Randy Heiland Date: Mon, 15 Dec 2025 06:08:53 -0500 Subject: [PATCH 2/5] warn about is_movable --- unit_tests/resume_sim/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/unit_tests/resume_sim/README.md b/unit_tests/resume_sim/README.md index c775a6568..300596a11 100644 --- a/unit_tests/resume_sim/README.md +++ b/unit_tests/resume_sim/README.md @@ -4,6 +4,7 @@ * does not handle intracellular model state * does not currently recover "output*_graph.txt" data; rather relies on the next mechanics step to recreate the information * if a model relies on global data generated in custom C++ code that is not captured in the MultiCellDS data, it cannot be recovered +* an agent's `is_movable` flag is not currently saved in the MultiCellDS (.mat), so it will not be recovered Normal simulation: From ae4e78ece9c5012ab441590790b7cba9d2d1917d Mon Sep 17 00:00:00 2001 From: Randy Heiland Date: Mon, 15 Dec 2025 08:35:11 -0500 Subject: [PATCH 3/5] cleanup; delete confusion --- modules/PhysiCell_MultiCellDS.cpp | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/modules/PhysiCell_MultiCellDS.cpp b/modules/PhysiCell_MultiCellDS.cpp index 1558b8cad..e6e57b963 100644 --- a/modules/PhysiCell_MultiCellDS.cpp +++ b/modules/PhysiCell_MultiCellDS.cpp @@ -1354,34 +1354,7 @@ int resume_from_MultiCellDS(std::string folder_path, std::string xml_filename, b return -1; } - // Let's confirm the last cell param, before the custom data vars, is what we expect. - // If/when this labels list is modified, this code will need to be updated. - - std::string label_path = "//cellular_information//cell_populations//custom//simplified_data//labels//label[@index=99]"; - std::cout << "\nreading " << label_path << std::endl; - // xpath_node = doc.select_node(label_path); // not allowed - - // - // - // xpath_node = doc.select_node("//cellular_information//cell_populations//custom//simplified_data//labels//label[@index=99]"); - // node = xpath_node.node(); - // if (node) - // { - // std::cout << "\n Success!\n" << std::endl; - // } - // else - // { - // std::cout << "\n --- Error reading node\n" << std::endl; - // return -1; - // } - // std::string last_var = xml_get_my_string_value(node); - // std::cout << "last_var= " << last_var << std::endl; - // if (last_var != "damage_repair_rate") - // { - // std::cout << "\n Error: last var should be 'damage_repair_rate'\n" << std::endl; - // return -1; - // } - + // read/skip over all labels until the last one, damage_repair_rate, so we can parse custom data vars xpath_node = doc.select_node("//cellular_information//cell_populations//custom//simplified_data//labels//label[@index=0]"); node = xpath_node.node(); if (!node) From 27e34134c42a0784a548e17dbe30c15edfc29e24 Mon Sep 17 00:00:00 2001 From: Randy Heiland Date: Mon, 15 Dec 2025 11:59:26 -0500 Subject: [PATCH 4/5] fix bug resuming custom data --- modules/PhysiCell_MultiCellDS.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/PhysiCell_MultiCellDS.cpp b/modules/PhysiCell_MultiCellDS.cpp index e6e57b963..40aac05e9 100644 --- a/modules/PhysiCell_MultiCellDS.cpp +++ b/modules/PhysiCell_MultiCellDS.cpp @@ -2146,7 +2146,10 @@ int recreate_sim_state(std::string filename, Microenvironment& M, // if it exists, overwrite if( idx_var > -1 ) { - pCell->custom_data.variables[idx_var].value = pair.second; + // pCell->custom_data.variables[idx_var].value = pair.second; + pCell->custom_data.variables[idx_var].value = dTemp; + if (debug_print) + { std::cout << " ---- pCell->custom_data[" << pair.first << "] = " <custom_data[pair.first] << std::endl; } } else { From 83619474d2bcc6905c5c0fe35266a35326b36566 Mon Sep 17 00:00:00 2001 From: Randy Heiland Date: Mon, 15 Dec 2025 14:31:14 -0500 Subject: [PATCH 5/5] improved resume main.cpp --- unit_tests/resume_sim/main.cpp | 156 +++++++++++++++++++-------------- 1 file changed, 90 insertions(+), 66 deletions(-) diff --git a/unit_tests/resume_sim/main.cpp b/unit_tests/resume_sim/main.cpp index 510d72212..7ca344767 100644 --- a/unit_tests/resume_sim/main.cpp +++ b/unit_tests/resume_sim/main.cpp @@ -86,57 +86,80 @@ using namespace PhysiCell; //----------------------------------------- int main( int argc, char* argv[] ) { - // load and parse settings file(s) + // load and parse settings file(s) - bool XML_status = false; - char copy_command [1024]; + bool XML_status = false; + char copy_command [1024]; + bool resume_from_checkpoint = false; std::string resume_from_dir; std::string resume_from_file; int next_full_index, next_svg_index; - // std::cout << "argc = " << argc << "argv=" << argv << std::endl; - if( argc < 5 ) - { - std::cout << "\n Too few arguments. Usage: <.xml config file> " << std::endl; - std::cout << " e.g. config/PhysiCell_settings.xml output output00000042.xml" << std::endl; - exit(-1); + std::cout << "argc = " << argc << ", argv[0]=" << argv[0] << std::endl; + if( argc == 6 ) + { + resume_from_checkpoint = true; - XML_status = load_PhysiCell_config_file( argv[1] ); - if( !XML_status ) - { exit(-1); } + XML_status = load_PhysiCell_config_file( argv[1] ); + if( !XML_status ) + { exit(-1); } - sprintf( copy_command , "cp %s %s" , argv[1] , PhysiCell_settings.folder.c_str() ); - // copy config file to output directry - system( copy_command ); + sprintf( copy_command , "cp %s %s" , argv[1] , PhysiCell_settings.folder.c_str() ); + // copy config file to output directry + system( copy_command ); resume_from_dir = argv[2]; resume_from_file = argv[3]; next_full_index = std::stoi(argv[4]); next_svg_index = std::stoi(argv[5]); - } - - // OpenMP setup - omp_set_num_threads(PhysiCell_settings.omp_num_threads); - - // time setup - std::string time_units = "min"; + } + else if( argc == 2 ) + { + XML_status = load_PhysiCell_config_file( argv[1] ); + sprintf( copy_command , "cp %s %s" , argv[1] , PhysiCell_settings.folder.c_str() ); + } + else if ( argc < 2 ) + { + XML_status = load_PhysiCell_config_file( "./config/PhysiCell_settings.xml" ); + sprintf( copy_command , "cp ./config/PhysiCell_settings.xml %s" , PhysiCell_settings.folder.c_str() ); + } + else + { + std::string err_msg = "\nIncorrect arguments. Usage options: \n" + "- no args: will try to use ./config/PhysiCell_settings.xml\n" + "- 1 arg: will try to use it as the config file\n" + "- 5 args to resume a simulation:\n" + " <.xml config file> \n"; - /* Microenvironment setup */ - setup_microenvironment(); // modify this in the custom code + std::cout << err_msg << std::endl; + std::cout << " e.g. config/PhysiCell_settings.xml output output00000042.xml 43 43\n" << std::endl; + exit(-1); + } - /* 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(); + if( !XML_status ) + { exit(-1); } + + // 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(); //--------- - bool resume_from_checkpoint = true; if ( !resume_from_checkpoint ) { setup_tissue(); @@ -150,6 +173,7 @@ int main( int argc, char* argv[] ) // int resume_from_MultiCellDS(std::string folder_path, std::string xml_filename, bool create_cells = true, bool debug_print = false); int retval = resume_from_MultiCellDS(resume_from_dir, resume_from_file); + // int retval = resume_from_MultiCellDS(resume_from_dir, resume_from_file, true, true); // Another option: don't recreate state, but print out state of cells // int retval = resume_from_MultiCellDS(resume_from_dir, resume_from_file, false, true); @@ -167,46 +191,46 @@ int main( int argc, char* argv[] ) PhysiCell_globals.SVG_output_index = next_svg_index; } - /* Users typically stop modifying here. END USERMODS */ + // Users typically stop modifying here. END USERMODS - // set MultiCellDS save options + // 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 + 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 - PhysiCell_SVG_options.length_bar = 200; + 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 ); - - sprintf( filename , "%s/legend.svg" , PhysiCell_settings.folder.c_str() ); - create_plot_legend( filename , cell_coloring_function ); - - display_citations(); - - // set the performance timers + std::vector (*cell_coloring_function)(Cell*) = my_coloring_function; + std::string (*substrate_coloring_function)(double, double, double) = paint_by_density_percentage; - BioFVM::RUNTIME_TIC(); - BioFVM::TIC(); - - std::ofstream report_file; - if( PhysiCell_settings.enable_legacy_saves == true ) + sprintf( filename , "%s/initial.svg" , PhysiCell_settings.folder.c_str() ); + SVG_plot( filename , microenvironment, 0.0 , PhysiCell_globals.current_time, cell_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() );