ZacrosParametersScanJob
***********************

"ZacrosParametersScanJob" class represents a job that is a container
for other jobs, called children jobs, which must be ZacrosJobs or
ZacrosSteadyStateJob kind objects. This class is an extension of the
PLAMS MultiJob class. So it inherits all its powerful features, e.g.,
being executed locally or submitted to some external queueing system
transparently or executing jobs in parallel with a predefined
dependency structure. See all configure possibilities on the PLAMS
MultiJob class documentation in this link: PLAMS.MultiJob.

The "ZacrosParametersScanJob" class constructor doesn't require a
Settings object; instead, it requires a reference job "reference" from
which the calculation "Settings" is taken to be replicated through its
children. Children are initially copies of the reference job. However,
just before they are run, their corresponding Settings are altered
accordingly to the rules defined through a "Parameters" object
provided in the constructor. The following lines illustrates how to
use this class:

   ps_parameters = pz.ZacrosParametersScanJob.Parameters()
   ps_parameters.add( 'x_CO', 'molar_fraction.CO', numpy.arange(0.1, 1.0, 0.1) )
   ps_parameters.add( 'x_O2', 'molar_fraction.O2', lambda params: 1.0-params['x_CO'] )
   ps_parameters.set_generator( pz.ZacrosParametersScanJob.zipGenerator )
   print(ps_parameters)

   ps_job = pz.ZacrosParametersScanJob( reference=z_job, parameters=ps_parameters )
   results = ps_job.run()

How to create the "Parameters" object is demonstrated in lines 1-4.
The object is initially formed by invoking its constructor (line 1).
Then, we created an independent variable called "x_CO" ranging from
0.1 to 0.9 in steps of 0.1, which will be substituted for
"molar_fraction.CO" in the Zacros input files (line 2). Then, we
created a dependent variable called "x_O2" that accepts the value
"1-x_CO" and will be replaced by "molar_fraction.O2" in the Zacros
input files. Keep in mind that this enforces the rule "x_CO+x_O2=1".
The 'zipGenerator' function from 'ZacrosParametersScanJob' is then
used as a generator. The independent variables are combined using the
zipGenerator function. However, since there is only one independent
parameter involved, this line can be skipped. It is only included here
to show how to call the generator. See the API documentation for more
details. Line 5, print the final values of the parameters. Finally,
lines 7 and 8 create the "ZacrosParametersScanJob" object by calling
its constructor and use its the method "run()" to execute it.

The execution of the code above generates the following output:

   1: {'x_CO': 0.1, 'x_O2': 0.9}
   2: {'x_CO': 0.2, 'x_O2': 0.8}
   3: {'x_CO': 0.3, 'x_O2': 0.7}
   4: {'x_CO': 0.4, 'x_O2': 0.6}
   5: {'x_CO': 0.5, 'x_O2': 0.5}
   6: {'x_CO': 0.6, 'x_O2': 0.4}
   7: {'x_CO': 0.7, 'x_O2': 0.3}
   8: {'x_CO': 0.8, 'x_O2': 0.2}
   9: {'x_CO': 0.9, 'x_O2': 0.1}

   [22.09|16:19:58] JOB plamsjob STARTED
   [22.09|16:19:58] JOB plamsjob RUNNING
   [22.09|16:19:58] JOB plamsjob/plamsjob_ps_cond000 STARTED
   [22.09|16:19:58] JOB plamsjob/plamsjob_ps_cond000 RUNNING
   [22.09|16:23:33] JOB plamsjob/plamsjob_ps_cond000 FINISHED
   [22.09|16:23:33] JOB plamsjob/plamsjob_ps_cond000 SUCCESSFUL
   [22.09|16:23:33] JOB plamsjob/plamsjob_ps_cond001 STARTED
   [22.09|16:23:33] JOB plamsjob/plamsjob_ps_cond001 RUNNING
   [22.09|16:27:16] JOB plamsjob/plamsjob_ps_cond001 FINISHED
   [22.09|16:27:17] JOB plamsjob/plamsjob_ps_cond001 SUCCESSFUL
   ...
   [22.09|16:47:18] JOB plamsjob/plamsjob_ps_cond008 STARTED
   [22.09|16:47:18] JOB plamsjob/plamsjob_ps_cond008 RUNNING
   [22.09|16:52:37] JOB plamsjob/plamsjob_ps_cond008 FINISHED
   [22.09|16:52:37] JOB plamsjob/plamsjob_ps_cond008 SUCCESSFUL
   [22.09|16:52:37] JOB plamsjob FINISHED
   [22.09|16:52:37] JOB plamsjob SUCCESSFUL

When running the "ZacrosParametersScanJob" calculation (see "run()"
method), all necessary input files for zacros are generated in the job
directory (see option "name" in the constructor), and Zacros is
internally executed. Then, all output files generated by Zacros are
stored for future reference in the same directory. The information in
the output directories can be easily accessed using the class
"ZacrosParametersScanResults".

In this example, all conditions or molar fractions of "CO" are
executed sequentially, but it is possible to execute in parallel using
a different JobRunner. Each condition is executed in a new job
directory, "plamsjob/plamsjob_ps_cond000",
"plamsjob/plamsjob_ps_cond001", etc., where all output files are
generated and stored for future reference. The "plams_job" prefix can
be replaced by using the option "name" in the constructor. Notice the
status of each job follows the sequence
"STARTED-->RUNNING-->FINISHED-->SUCCESSFUL".


API
===

class ZacrosParametersScanJob(reference, parameters=None, **kwargs)

   Creates a new ZacrosParametersScanJob object. This class is a job
   that is a container for other jobs, called children jobs and it is
   an extension of the PLAMS.MultiJob. Children are copies of a
   reference job "reference". However, just before they are run, their
   corresponding Settings are altered accordingly to the rules defined
   through the "Parameters" object "parameters".

   * "reference" -- Reference job. It must be ZacrosJob or
     ZacrosSteadyStateJob kind object.

   * "parameters" -- "Parameters" object containing the parameters'
     specifications.

   * "name" -- A string containing the name of the job. All zacros
     input and output files are stored in a folder with this name. If
     not supplied, the default name is "plamsjob".

   run(jobrunner: JobRunner | None = None, jobmanager: JobRunner | None = None, **kwargs) -> Results

      Run the job. Raises a "ZacrosExecutableNotFoundError" if the
      Zacros executable cannot be found.

   check()

      Check if the execution of this instance was successful, by
      calling "Job.ok()" of all the children jobs.

   static zipGenerator(reference_settings, parameters)

      This function combines the values of the parameters one-to-one
      following the order as they were defined

      * "reference_settings" -- "Settings" object to be used as a
        reference.

      * "parameters" -- "Parameters" object containing the parameters'
        specifications.

      Example of use:

         params = pz.ZacrosParametersScanJob.Parameters()
         params.add( 'x_CO', 'molar_fraction.CO', numpy.arange(0.0, 1.0, 0.25) )
         params.add( 'x_O2', 'molar_fraction.O2', lambda p: 1.0-p['x_CO'] )
         params.set_generator( pz.ZacrosParametersScanJob.zipGenerator )
         print(params)

      The code above will generate the following output that lists the
      final values for the parameters:

         0: {'x_CO': 0.0, 'x_O2': 1.0}
         1: {'x_CO': 0.25, 'x_O2': 0.75}
         2: {'x_CO': 0.5, 'x_O2': 0.5}
         3: {'x_CO': 0.75, 'x_O2': 0.25}

   static meshgridGenerator(reference_settings, parameters)

      This function combines the values of the parameters creating an
      *n-`dimensional rectangular grid, being `n* the number of
      parameters. Meshgrid generator is inspired by "numpy.meshgrid"
      function.

      * "reference_settings" -- "Settings" object to be used as a
        reference.

      * "parameters" -- "Parameters" object containing the
        specifications of the parameters.

      Example of use:

         params = pz.ZacrosParametersScanJob.Parameters()
         params.add( 'x_CO', 'molar_fraction.CO', numpy.arange(0.0, 1.0, 0.4) )
         params.add( 'x_O2', 'molar_fraction.O2', numpy.arange(0.0, 1.0, 0.4) )
         params.add( 'x_N2', 'molar_fraction.N2', lambda p: 0.11+p['x_CO']+p['x_O2'] )
         params.set_generator( pz.ZacrosParametersScanJob.meshgridGenerator )
         print(params)

      The code above will generate the following output that lists the
      final values for the parameters:

         (0, 0): {'x_CO': 0.0, 'x_O2': 0.0, 'x_N2': 0.11}
         (0, 1): {'x_CO': 0.4, 'x_O2': 0.0, 'x_N2': 0.51}
         (0, 2): {'x_CO': 0.8, 'x_O2': 0.0, 'x_N2': 0.91}
         (1, 0): {'x_CO': 0.0, 'x_O2': 0.4, 'x_N2': 0.51}
         (1, 1): {'x_CO': 0.4, 'x_O2': 0.4, 'x_N2': 0.91}
         (1, 2): {'x_CO': 0.8, 'x_O2': 0.4, 'x_N2': 1.31}
         (2, 0): {'x_CO': 0.0, 'x_O2': 0.8, 'x_N2': 0.91}
         (2, 1): {'x_CO': 0.4, 'x_O2': 0.8, 'x_N2': 1.31}
         (2, 2): {'x_CO': 0.8, 'x_O2': 0.8, 'x_N2': 1.71}

class ZacrosParametersScanJob.Parameter(name_in_settings, kind, values)

   Creates a new Parameter object specifically tailored for
   ZacrosParametersScanJob

class ZacrosParametersScanJob.Parameters(*args, **kwargs)

   Creates a new Parameters object specifically tailored for
   ZacrosParametersScanJob
