diff --git a/CHANGELOG.md b/CHANGELOG.md index 01115016825a8..d13597fc1c8b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Current develop ### Added (new features/APIs/variables/...) +- [[PR 1362]](https://github.com/parthenon-hpc-lab/parthenon/pull/1362) Add support for completely custom "user" output - [[PR 1344]](https://github.com/parthenon-hpc-lab/parthenon/pull/1344) Add option to communicate single layer of ghosts, only communicate required two-level composite boundaries diff --git a/doc/sphinx/src/outputs.rst b/doc/sphinx/src/outputs.rst index 8a511a112e2f2..f8948edc05300 100644 --- a/doc/sphinx/src/outputs.rst +++ b/doc/sphinx/src/outputs.rst @@ -406,6 +406,37 @@ The following is a minimal example to plot a 1D and 2D histogram from the output plt.pcolormesh(x,y,z,) plt.show() +User +---- + +Parthenon provides a simple callback for downstream codes to enroll their own +output routines. +In the input file, include a ```` block and specify +``file_type = my_user_type`` where the latter is an arbitrary string. +Output frequency is controlld via ``dt`` or ``dn`` as for other output types. +Using this callback requires a downstream code to implement and enroll a derived +``OutputType`` object with the ``my_user_type`` string name. + +.. code:: c++ + + pman.app_input->RegisterUserOutput("my_user_type", std::make_shared()); + +where ``MyOutput`` has to minimally implement ``WriteOutputFile`` + +.. code:: c++ + + class MyOutput : public OutputType { + public: + explicit MyOutput() : OutputType({}) {} + void WriteOutputFile(Mesh *pm, ParameterInput *pin, SimTime *tm, + const SignalHandler::OutputSignal signal) override; + }; + +A minimal implementation is demonstrated in the +`particle_leapfrog `__ +example. + + Ascent (optional) ----------------- diff --git a/example/particle_leapfrog/main.cpp b/example/particle_leapfrog/main.cpp index 450d2b1230fc8..684a3bf3f93eb 100644 --- a/example/particle_leapfrog/main.cpp +++ b/example/particle_leapfrog/main.cpp @@ -1,4 +1,8 @@ //======================================================================================== +// Parthenon performance portable AMR framework +// Copyright(C) 2021-2026 The Parthenon collaboration +// Licensed under the 3-clause BSD License, see LICENSE file for details +//======================================================================================== // (C) (or copyright) 2020-2023. Triad National Security, LLC. All rights reserved. // // This program was produced under U.S. Government contract 89233218CNA000001 for Los @@ -11,8 +15,9 @@ // the public, perform publicly and display publicly, and to permit others to do so. //======================================================================================== -#include "parthenon_manager.hpp" +#include +#include "parthenon_manager.hpp" #include "particle_leapfrog.hpp" int main(int argc, char *argv[]) { @@ -24,6 +29,10 @@ int main(int argc, char *argv[]) { pman.app_input->ProcessPackages = particles_leapfrog::ProcessPackages; pman.app_input->ProblemGenerator = particles_leapfrog::ProblemGenerator; + // Create user output and enroll + pman.app_input->RegisterUserOutput( + "particle_user_output", std::make_shared()); + // call ParthenonInit to initialize MPI and Kokkos, parse the input deck, and set up auto manager_status = pman.ParthenonInitEnv(argc, argv); if (manager_status == ParthenonStatus::complete) { diff --git a/example/particle_leapfrog/particle_leapfrog.cpp b/example/particle_leapfrog/particle_leapfrog.cpp index cfe5c7cf67f6e..16aebe4d3c1a2 100644 --- a/example/particle_leapfrog/particle_leapfrog.cpp +++ b/example/particle_leapfrog/particle_leapfrog.cpp @@ -1,6 +1,6 @@ //======================================================================================== // Parthenon performance portable AMR framework -// Copyright(C) 2021-2024 The Parthenon collaboration +// Copyright(C) 2021-2026 The Parthenon collaboration // Licensed under the 3-clause BSD License, see LICENSE file for details //======================================================================================== // (C) (or copyright) 2020-2024. Triad National Security, LLC. All rights reserved. @@ -47,6 +47,36 @@ Packages_t ProcessPackages(std::unique_ptr &pin) { return packages; } +// *************************************************// +// Example (very simple) on how to enroll a custom output function that follows +// all the standard logic (i.e., time, cycle or signal based) and can implement +// any kind of logic/output. +// It is highly recommend to look at the other default Parthenon internal OutputTypes +// for more detailed code on typical output boilerplate. +// *************************************************// +void ParticleUserOutput::WriteOutputFile(Mesh *pm, ParameterInput *pin, SimTime *tm, + const SignalHandler::OutputSignal signal) { + // For simplicitly, only rank 0 writes the current cycle. + // Typically writing is a parallel operation. + if (Globals::my_rank == 0) { + std::ofstream outfile("user_output." + std::to_string(output_params.file_number)); + if (outfile.is_open()) { + outfile << "cycle = " << tm->ncycle; + outfile.close(); + } + } + + // Advance file ids and times. + // This has to be done on all ranks (so no rank filter) for consistency. + if (signal == SignalHandler::OutputSignal::none) { + // After file has been opened with the current number, already advance output + // parameters so that for restarts the file is not immediatly overwritten again. + // Only applies to default time-based data dumps, so that writing "now" and "final" + // outputs does not change the desired output numbering. + UpdateNextOutput_(pm, tm); + } +} + // *************************************************// // define the "physics" package particles_package, *// // which includes defining various functions that *// diff --git a/example/particle_leapfrog/particle_leapfrog.hpp b/example/particle_leapfrog/particle_leapfrog.hpp index dbe910afd7212..58fe5e4009507 100644 --- a/example/particle_leapfrog/particle_leapfrog.hpp +++ b/example/particle_leapfrog/particle_leapfrog.hpp @@ -1,6 +1,6 @@ //======================================================================================== // Parthenon performance portable AMR framework -// Copyright(C) 2021 The Parthenon collaboration +// Copyright(C) 2021-2026 The Parthenon collaboration // Licensed under the 3-clause BSD License, see LICENSE file for details //======================================================================================== // (C) (or copyright) 2020-2021. Triad National Security, LLC. All rights reserved. @@ -45,6 +45,17 @@ class ParticleDriver : public EvolutionDriver { void ProblemGenerator(MeshBlock *pmb, ParameterInput *pin); Packages_t ProcessPackages(std::unique_ptr &pin); +//---------------------------------------------------------------------------------------- +//! \class ParticleUserOutput +// \brief derived OutputType class for User enrolled outputs + +class ParticleUserOutput : public OutputType { + public: + explicit ParticleUserOutput() : OutputType({}) {} + void WriteOutputFile(Mesh *pm, ParameterInput *pin, SimTime *tm, + const SignalHandler::OutputSignal signal) override; +}; + namespace Particles { std::shared_ptr Initialize(ParameterInput *pin); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4d8aeaf8c8d44..3f0905770f607 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -146,6 +146,7 @@ add_library(parthenon interface/meshblock_data.hpp interface/metadata.cpp interface/metadata.hpp + interface/outputs.hpp interface/packages.hpp interface/params.cpp interface/params.hpp @@ -202,7 +203,6 @@ add_library(parthenon outputs/outputs.hpp outputs/outputs_package.cpp outputs/outputs_package.hpp - outputs/output_parameters.hpp outputs/parthenon_hdf5.cpp outputs/parthenon_hdf5_attributes.cpp outputs/parthenon_hdf5_attributes_read.cpp diff --git a/src/application_input.cpp b/src/application_input.cpp index 42f7eb116272c..e7f2a2c9860bf 100644 --- a/src/application_input.cpp +++ b/src/application_input.cpp @@ -1,4 +1,8 @@ //======================================================================================== +// Parthenon performance portable AMR framework +// Copyright(C) 2020-2026 The Parthenon collaboration +// Licensed under the 3-clause BSD License, see LICENSE file for details +//======================================================================================== // (C) (or copyright) 2020-2024. Triad National Security, LLC. All rights reserved. // // This program was produced under U.S. Government contract 89233218CNA000001 for Los @@ -11,6 +15,7 @@ // the public, perform publicly and display publicly, and to permit others to do so. //======================================================================================== +#include #include #include "application_input.hpp" @@ -103,5 +108,26 @@ SBValFunc ApplicationInput::GetSwarmBoundaryCondition(BoundaryFace face, } return swarm_boundary_conditions_[face].at(name); } +void ApplicationInput::RegisterUserOutput(const std::string &name, + std::shared_ptr output_type) { + PARTHENON_REQUIRE_THROWS(!(name == "hst" || name == "rst" || name == "hdf5" || + name == "corehdf5" || name == "histogram" || + name == "ascent"), + "Trying to enroll UserOutput with name in conflict with " + "internal Parthenon file_type."); + if (user_outputs_.count(name) > 0) { + PARTHENON_THROW("User OutputType " + name + " already registered."); + } + user_outputs_[name] = output_type; +} + +// Returns pointer to user output (if it exists) or nullptr if not found. +std::shared_ptr ApplicationInput::GetUserOutput(const std::string &name) { + if (user_outputs_.count(name) == 0) { + return nullptr; + } else { + return user_outputs_[name]; + } +} } // namespace parthenon diff --git a/src/application_input.hpp b/src/application_input.hpp index 9c91585bfb3e8..9ca8903b548b7 100644 --- a/src/application_input.hpp +++ b/src/application_input.hpp @@ -1,4 +1,8 @@ //======================================================================================== +// Parthenon performance portable AMR framework +// Copyright(C) 2020-2026 The Parthenon collaboration +// Licensed under the 3-clause BSD License, see LICENSE file for details +//======================================================================================== // (C) (or copyright) 2020-2024. Triad National Security, LLC. All rights reserved. // // This program was produced under U.S. Government contract 89233218CNA000001 for Los @@ -22,6 +26,7 @@ #include "basic_types.hpp" #include "bvals/boundary_conditions.hpp" #include "defs.hpp" +#include "interface/outputs.hpp" #include "parthenon_arrays.hpp" namespace parthenon { @@ -93,9 +98,16 @@ class ApplicationInput { BValFunc GetBoundaryCondition(BoundaryFace face, const std::string &name) const; SBValFunc GetSwarmBoundaryCondition(BoundaryFace face, const std::string &name) const; + // Custom user outputs + void RegisterUserOutput(const std::string &name, + std::shared_ptr output_type); + std::shared_ptr GetUserOutput(const std::string &name); + private: Dictionary boundary_conditions_[BOUNDARY_NFACES]; Dictionary swarm_boundary_conditions_[BOUNDARY_NFACES]; + + Dictionary> user_outputs_; }; } // namespace parthenon diff --git a/src/driver/driver.hpp b/src/driver/driver.hpp index 1fe184ca34e0d..95ed00b36fb6b 100644 --- a/src/driver/driver.hpp +++ b/src/driver/driver.hpp @@ -40,7 +40,9 @@ class Driver { Driver(ParameterInput *pin, ApplicationInput *app_in, Mesh *pm) : pinput(pin), app_input(app_in), pmesh(pm), mbcnt_prev(), time_LBandAMR() {} virtual DriverStatus Execute() = 0; - void InitializeOutputs() { pouts = std::make_unique(pmesh, pinput); } + void InitializeOutputs() { + pouts = std::make_unique(pmesh, pinput, app_input); + } void DumpInputParameters(); ParameterInput *pinput; @@ -121,7 +123,7 @@ class EvolutionDriver : public Driver { const auto nout_mesh = pinput->GetOrAddInteger("parthenon/time", "ncycle_out_mesh", 0, "cadence of outputs describing mesh"); tm = SimTime(start_time, tstop, nmax, ncycle, nout, nout_mesh, dt); - pouts = std::make_unique(pmesh, pinput, &tm); + pouts = std::make_unique(pmesh, pinput, app_in, &tm); output_before_amr = pinput->GetOrAddBoolean( "parthenon/time", "output_before_amr", false, diff --git a/src/outputs/output_parameters.hpp b/src/interface/outputs.hpp similarity index 63% rename from src/outputs/output_parameters.hpp rename to src/interface/outputs.hpp index a7d9c470c1e87..7fe6465f03f3f 100644 --- a/src/outputs/output_parameters.hpp +++ b/src/interface/outputs.hpp @@ -1,6 +1,6 @@ //======================================================================================== // Parthenon performance portable AMR framework -// Copyright(C) 2020-2025 The Parthenon collaboration +// Copyright(C) 2020-2026 The Parthenon collaboration // Licensed under the 3-clause BSD License, see LICENSE file for details //======================================================================================== // Athena++ astrophysical MHD code @@ -18,17 +18,24 @@ // license in this material to reproduce, prepare derivative works, distribute copies to // the public, perform publicly and display publicly, and to permit others to do so. //======================================================================================== - -#ifndef OUTPUTS_OUTPUT_PARAMETERS_HPP_ -#define OUTPUTS_OUTPUT_PARAMETERS_HPP_ +#ifndef INTERFACE_OUTPUTS_HPP_ +#define INTERFACE_OUTPUTS_HPP_ +//! \file outputs.hpp +// \brief provides base classes to handle ALL types of data output #include #include #include #include +#include "basic_types.hpp" + namespace parthenon { +// forward declarations +class Mesh; +class ParameterInput; + // JMM: I designed this for HDF5 but in pinciple this switching could // also work for other output types... Any output type that is capable // of outputting a full dump can do this. @@ -74,6 +81,41 @@ struct OutputParameters { int file_number = 0; }; -} // namespace parthenon +//---------------------------------------------------------------------------------------- +// \brief abstract base class for different output types (modes/formats). Each OutputType +// is designed to be a node in a singly linked list created & stored in the Outputs class + +class OutputType { + public: + // mark single parameter constructors as "explicit" to prevent them from acting as + // implicit conversion functions: for f(OutputType arg), prevent f(anOutputParameters) + explicit OutputType(OutputParameters oparams); + + // rule of five: + virtual ~OutputType() = default; + // copied) + OutputType(const OutputType ©_other) = default; + OutputType &operator=(const OutputType ©_other) = default; + // move constructor and assignment operator + OutputType(OutputType &&) = default; + OutputType &operator=(OutputType &&) = default; -#endif // OUTPUTS_OUTPUT_PARAMETERS_HPP_ + // data + OutputParameters output_params; // control data read from block + + // following pure virtual function must be implemented in all derived classes + virtual void WriteOutputFile(Mesh *pm, ParameterInput *pin, SimTime *tm, + const SignalHandler::OutputSignal signal) = 0; + virtual void WriteContainer(SimTime &tm, Mesh *pm, ParameterInput *pin, bool flag) { + return; + } + + protected: + int num_vars_; // number of variables in output + + // Update book-keeping such as next output time to next output + void UpdateNextOutput_(Mesh *pm, SimTime *tm); +}; + +} // namespace parthenon +#endif // INTERFACE_OUTPUTS_HPP_ diff --git a/src/interface/state_descriptor.hpp b/src/interface/state_descriptor.hpp index cd35cf0c5eae8..7c570c8f6e688 100644 --- a/src/interface/state_descriptor.hpp +++ b/src/interface/state_descriptor.hpp @@ -28,10 +28,10 @@ #include "basic_types.hpp" #include "bvals/boundary_conditions.hpp" #include "interface/metadata.hpp" +#include "interface/outputs.hpp" #include "interface/params.hpp" #include "interface/sparse_pool.hpp" #include "interface/var_id.hpp" -#include "outputs/output_parameters.hpp" #include "pack/scratch_variables.hpp" #include "parameter_input.hpp" #include "prolong_restrict/prolong_restrict.hpp" diff --git a/src/outputs/output_utils.hpp b/src/outputs/output_utils.hpp index 73011c6cbdf91..a6689ee21eb08 100644 --- a/src/outputs/output_utils.hpp +++ b/src/outputs/output_utils.hpp @@ -36,12 +36,12 @@ #include "basic_types.hpp" #include "defs.hpp" #include "interface/metadata.hpp" +#include "interface/outputs.hpp" #include "interface/variable.hpp" #include "kokkos_abstraction.hpp" #include "mesh/domain.hpp" #include "mesh/mesh.hpp" #include "mesh/meshblock.hpp" -#include "outputs/output_parameters.hpp" #include "utils/error_checking.hpp" namespace parthenon { diff --git a/src/outputs/outputs.cpp b/src/outputs/outputs.cpp index 0062cefc098d4..325f8b7858a73 100644 --- a/src/outputs/outputs.cpp +++ b/src/outputs/outputs.cpp @@ -69,12 +69,13 @@ #include #include +#include "application_input.hpp" #include "coordinates/coordinates.hpp" #include "defs.hpp" #include "globals.hpp" +#include "interface/outputs.hpp" #include "mesh/mesh.hpp" #include "mesh/meshblock.hpp" -#include "outputs/output_parameters.hpp" #include "pack/swarm_default_names.hpp" #include "parameter_input.hpp" #include "parthenon_arrays.hpp" @@ -91,7 +92,7 @@ OutputType::OutputType(OutputParameters oparams) : output_params(oparams), num_v //---------------------------------------------------------------------------------------- // Outputs constructor -Outputs::Outputs(Mesh *pm, ParameterInput *pin, SimTime *tm) { +Outputs::Outputs(Mesh *pm, ParameterInput *pin, ApplicationInput *papp_in, SimTime *tm) { std::stringstream msg; // We should only have at most one each of these output types. Count // them so we can raise an error. @@ -365,6 +366,8 @@ Outputs::Outputs(Mesh *pm, ParameterInput *pin, SimTime *tm) { << std::endl; PARTHENON_FAIL(msg); #endif // ifdef ENABLE_HDF5 + // } else if (op.file_type == "user") { + // pnew_type = std::make_shared(op); } else if (is_hdf5_output) { restart = (op.file_type == "rst"); const bool coredump = (op.file_type == "corehdf5"); @@ -389,11 +392,18 @@ Outputs::Outputs(Mesh *pm, ParameterInput *pin, SimTime *tm) { << std::endl; PARTHENON_FAIL(msg); #endif // ifdef ENABLE_HDF5 + // Finally, check if block is an enrolled user output. } else { - msg << "### FATAL ERROR in Outputs constructor" << std::endl - << "Unrecognized file format = '" << op.file_type << "' in output block '" - << op.block_name << "'" << std::endl; - PARTHENON_FAIL(msg); + pnew_type = papp_in->GetUserOutput(op.file_type); + if (pnew_type) { + // Update empty OutputParams op with actual ones + pnew_type->output_params = op; + } else { + msg << "### FATAL ERROR in Outputs constructor" << std::endl + << "Unrecognized file format = '" << op.file_type << "' in output block '" + << op.block_name << "'" << std::endl; + PARTHENON_FAIL(msg); + } } // Append type diff --git a/src/outputs/outputs.hpp b/src/outputs/outputs.hpp index fe375c9ba93c6..3aff2213d2afd 100644 --- a/src/outputs/outputs.hpp +++ b/src/outputs/outputs.hpp @@ -1,6 +1,6 @@ //======================================================================================== // Parthenon performance portable AMR framework -// Copyright(C) 2020-2025 The Parthenon collaboration +// Copyright(C) 2020-2026 The Parthenon collaboration // Licensed under the 3-clause BSD License, see LICENSE file for details //======================================================================================== // Athena++ astrophysical MHD code @@ -31,12 +31,13 @@ #include "Kokkos_ScatterView.hpp" +#include "application_input.hpp" #include "basic_types.hpp" #include "coordinates/coordinates.hpp" #include "interface/mesh_data.hpp" +#include "interface/outputs.hpp" #include "io_wrapper.hpp" #include "kokkos_abstraction.hpp" -#include "outputs/output_parameters.hpp" #include "parthenon_arrays.hpp" #include "utils/error_checking.hpp" @@ -46,42 +47,6 @@ namespace parthenon { class Mesh; class ParameterInput; -//---------------------------------------------------------------------------------------- -// \brief abstract base class for different output types (modes/formats). Each OutputType -// is designed to be a node in a singly linked list created & stored in the Outputs class - -class OutputType { - public: - // mark single parameter constructors as "explicit" to prevent them from acting as - // implicit conversion functions: for f(OutputType arg), prevent f(anOutputParameters) - explicit OutputType(OutputParameters oparams); - - // rule of five: - virtual ~OutputType() = default; - // copied) - OutputType(const OutputType ©_other) = default; - OutputType &operator=(const OutputType ©_other) = default; - // move constructor and assignment operator - OutputType(OutputType &&) = default; - OutputType &operator=(OutputType &&) = default; - - // data - OutputParameters output_params; // control data read from block - - // following pure virtual function must be implemented in all derived classes - virtual void WriteOutputFile(Mesh *pm, ParameterInput *pin, SimTime *tm, - const SignalHandler::OutputSignal signal) = 0; - virtual void WriteContainer(SimTime &tm, Mesh *pm, ParameterInput *pin, bool flag) { - return; - } - - protected: - int num_vars_; // number of variables in output - - // Update book-keeping such as next output time to next output - void UpdateNextOutput_(Mesh *pm, SimTime *tm); -}; - //---------------------------------------------------------------------------------------- // Helper definitions to enroll user output variables @@ -250,7 +215,8 @@ class HistogramOutput : public OutputType { class Outputs { public: - Outputs(Mesh *pm, ParameterInput *pin, SimTime *tm = nullptr); + Outputs(Mesh *pm, ParameterInput *pin, ApplicationInput *papp_in, + SimTime *tm = nullptr); void MakeOutputs(Mesh *pm, ParameterInput *pin, SimTime *tm = nullptr, diff --git a/src/outputs/parthenon_hdf5.cpp b/src/outputs/parthenon_hdf5.cpp index 2be1054855978..d9888454c1286 100644 --- a/src/outputs/parthenon_hdf5.cpp +++ b/src/outputs/parthenon_hdf5.cpp @@ -1,10 +1,6 @@ //======================================================================================== // Parthenon performance portable AMR framework -// Copyright(C) 2020-2025 The Parthenon collaboration -// Licensed under the 3-clause BSD License, see LICENSE file for details -//======================================================================================== -// Parthenon performance portable AMR framework -// Copyright(C) 2020-2025 The Parthenon collaboration +// Copyright(C) 2020-2026 The Parthenon collaboration // Licensed under the 3-clause BSD License, see LICENSE file for details //======================================================================================== // (C) (or copyright) 2020-2025. Triad National Security, LLC. All rights reserved. @@ -40,9 +36,9 @@ #include "driver/driver.hpp" #include "interface/metadata.hpp" +#include "interface/outputs.hpp" #include "mesh/mesh.hpp" #include "mesh/meshblock.hpp" -#include "outputs/output_parameters.hpp" #include "outputs/output_utils.hpp" #include "outputs/outputs.hpp" #include "outputs/parthenon_hdf5.hpp" diff --git a/tst/regression/test_suites/particle_leapfrog_outflow/parthinput.particle_leapfrog_outflow b/tst/regression/test_suites/particle_leapfrog_outflow/parthinput.particle_leapfrog_outflow index 613d222cc2b9f..5784aade2022c 100644 --- a/tst/regression/test_suites/particle_leapfrog_outflow/parthinput.particle_leapfrog_outflow +++ b/tst/regression/test_suites/particle_leapfrog_outflow/parthinput.particle_leapfrog_outflow @@ -57,3 +57,7 @@ file_type = hdf5 dt = 2.0 swarms = my_particles my_particles_variables = id, v, vv + + +file_type = particle_user_output # this is a custom user output defined by the application +dt = 2.0 \ No newline at end of file diff --git a/tst/regression/test_suites/particle_leapfrog_outflow/particle_leapfrog_outflow.py b/tst/regression/test_suites/particle_leapfrog_outflow/particle_leapfrog_outflow.py index 8a490a4b1cc5c..f326760389e55 100644 --- a/tst/regression/test_suites/particle_leapfrog_outflow/particle_leapfrog_outflow.py +++ b/tst/regression/test_suites/particle_leapfrog_outflow/particle_leapfrog_outflow.py @@ -19,6 +19,7 @@ import numpy as np from numpy.lib.recfunctions import structured_to_unstructured +import os import sys import utils.test_case @@ -55,7 +56,33 @@ def Analyse(self, parameters): [-0.1, 0.3, 0.475, 0.0, 0.0, 0.5], ] ) + + success = True if ref_data.shape != final_data.shape: print("TEST FAIL: Mismatch between actual and reference data shape.") - return False - return (np.abs(final_data - ref_data) <= 1e-10).all() + success = False + if not (np.abs(final_data - ref_data) <= 1e-10).all(): + print("TEST FAIL: Error between actual and reference data too large.") + success = False + + if not os.path.isfile("user_output.0"): + print("TEST FAIL: Missing initial custom enrolled user output.") + success = False + else: + with open("user_output.0", "r") as infile: + line = infile.readline() + if line != "cycle = 0": + print("TEST FAIL: Wrong content in initial user outfile.", line) + success = False + + if not os.path.isfile("user_output.1"): + print("TEST FAIL: Missing final custom enrolled user output.") + success = False + else: + with open("user_output.1", "r") as infile: + line = infile.readline() + if line != "cycle = 50": + print("TEST FAIL: Wrong content in final user outfile.", line) + success = False + + return success