function job = batch(scriptName, varargin)
%BATCH Run MATLAB function or script on worker
%   j = BATCH('aScript') runs the script aScript.m on a worker according to
%   the cluster defined in the default parallel profile.  The function
%   returns j, a handle to the job object that runs the script. The script
%   file aScript.m is copied to the worker.
%
%   j = BATCH(myCluster, 'aScript') is identical to BATCH('aScript') except
%   that the script runs on a worker using the cluster identified by the
%   object myCluster.
%
%   j = BATCH(fcn, N, {x1,..., xn}) runs the function specified by a
%   function handle or function name, fcn, on a worker according to the
%   cluster defined in the default parallel profile.  The function returns
%   j, a handle to the job object that runs the function. The function is
%   evaluated with the given arguments, x1,...,xn, returning N output
%   arguments.  The function file for fcn is copied to the worker.
%
%   j = BATCH(myCluster, fcn, N, {x1,..., xn}) is identical to BATCH(fcn,
%   N, {x1,..., xn}) except that the function runs on a worker using the
%   cluster identified by the object myCluster.
%
%   j = BATCH( ..., P1, V1, ..., Pn, Vn) allows additional parameter-value
%   pairs that modify the behavior of the job.  These parameters support
%   batch for functions and scripts, unless otherwise indicated.  The
%   accepted parameters are:
%
%   - 'Workspace' - A 1-by-1 struct to define the workspace on the worker
%     just before the script is called. The field names of the struct
%     define the names of the variables, and the field values are assigned
%     to the workspace variables. By default this parameter has a field for
%     every variable in the current workspace where batch is executed. This
%     parameter can only be used with scripts.
%
%   - 'Profile' - A single character vector or string that is the name of
%     a parallel profile to use to identify the cluster.  If this option
%     is omitted, the default profile is used to identify the cluster and
%     is applied to the job and task properties.
%
%   - 'AdditionalPaths' - A character vector, string, string array, or cell
%     array of character vectors that defines paths to be added to the workers'
%     MATLAB path before the script or function is executed.
%
%   - 'AttachedFiles' - A character vector, string, string array, or cell
%     array of character vectors.  Each entry in the list identifies either
%     a file or a folder, which is transferred to the worker.
%
%   - 'AutoAddClientPath' - A logical value (true or false) that controls
%     whether user-added-entries on the client's path are added to each 
%     worker's path.  The default is true.
%
%   - 'AutoAttachFiles' - A logical value to specify whether code files
%     should be automatically attached to the job. If true, the script or
%     function is analyzed and the code files it depends on are
%     automatically transferred to the worker. The default is true. 
%
%   - 'CurrentFolder' - A character vector or string to specify which folder
%     the script executes in. There is no guarantee that this folder exists on
%     the worker. The default value for this property is the current folder
%     of MATLAB when the batch command is executed. If the value for this
%     argument is '.', there is no change in folder before batch execution.
%
%   - 'CaptureDiary' - A logical value to specify whether diary output
%     should be retrieved from the script execution or function call.  See 
%     the DIARY function for how to return this information to the client. 
%     The default is true.
%
%   - 'Pool' - A nonnegative integer or a range specified as a 2-element
%     vector of integers that defines the size of the parallel pool to use
%     when running the job. If the value is a range, the resulting pool has
%     size as large as possible in the range requested. This value
%     overrides the NumWorkersRange specified in the profile. The default
%     is 0, which causes the script or function to run on only the single
%     worker without a pool.
%
%   - 'SpmdEnabled' - a logical value (true or false). Flag to specify if spmd
%     support is enabled on the parallel pool when you specify the 'Pool'
%     name-value argument to create a parallel pool.
%
%   - 'EnvironmentVariables' - A character vector, string, string array, or cell
%     array of character vectors that defines the names of environment variables
%     which will be copied from the client session to the workers.
%
%   Examples:
%   % Run a batch script on a worker:
%   j = batch('script1');
%
%   % Run a batch script, capturing the diary, adding a path to the workers
%   % and transferring some required files
%   j = batch('script1', ...
%             'AdditionalPaths', '\\Shared\Project1\HelperFiles',...
%             'AttachedFiles', {'script1helper1', 'script1helper2'});
%   % Wait for the job to finish
%   wait(j)
%   % Display the diary
%   diary(j)
%   % Get the results of running the script in this workspace
%   load(j)
%
%   % Run a batch job on a remote cluster using a pool of 8 workers:
%   j = batch('script1', 'Pool', 8);
%
%   % Run a batch job on a local worker, which employs two other
%   % local workers:
%   j = batch('script1', 'Profile', 'local', 'Pool', 2);
%
%   % Run a batch function on a remote cluster defined by the profile
%   % myProfile that generates a 10-by-10 random matrix
%   myCluster = parcluster('myProfile');
%   j = batch(myCluster, @rand, 1, {10, 10});
%   % Wait for the job to finish
%   wait(j)
%   % Display the diary
%   diary(j)
%   % Get the results of running the job into a cell array
%   r = fetchOutputs(j)
%   % Get the generated random number from r
%   r{1}
%
%   % Run some batch scripts and then use findJob to retrieve the jobs.
%   batch(myCluster, 'script1');
%   batch(myCluster, 'script2');
%   batch(myCluster, 'script3');
%   myBatchJobs = findJob(myCluster, 'Username', 'myUsername')
%
%   See also parallel.Job/wait, parallel.Job/load, parallel.Job/diary,
%            parallel.Cluster/batch, parallel.Cluster/Jobs, 
%            parallel.Cluster/findJob, parcluster.

%   Copyright 2007-2023 The MathWorks, Inc.

import parallel.internal.apishared.ProfileConfigHelper

% Convert any string inputs to character vectors
[scriptName, varargin{:}] = convertStringsToChars(scriptName, varargin{:});

% Ensure that we have between 1 and Inf input arguments
% (must have a script name), but don't use standard narginchk so users don't
% get confused between input arguments to batch and nargin/nargout
% required by their function handle.
parallel.internal.cluster.checkNumberOfArguments('input', 1, inf, nargin, mfilename);

constructor  = @parallel.internal.cluster.BatchHelper2;
pch          = ProfileConfigHelper.buildApi2();

% Parse the inputs
try
    batchHelper = constructor(pch, scriptName, varargin);
catch err
    % Make all errors appear from batch
    throw(err);
end

% Deal with the Workspace and configuration.  Set the WorkspaceIn to the
% caller if one wasn't supplied.  Note that this code has to exist here
% because multiple evalin calls cannot be nested.  This code can't even be
% put into a script, because evalin('caller') from the script just gets
% this function's workspace and not the caller of this function.  If you
% change this code, make sure you change the versions in @jobmanager/batch.m
% and parallel.Cluster.batch as well.
if batchHelper.needsCallerWorkspace
    where = 'caller';
    % No workspace supplied - we need to make our own from the calling workspace
    vars = evalin(where, 'whos');
    % Loop over each variable in the calling workspace to get its value
    numVars = numel(vars);
    varNames = cell(numVars, 1);
    varValues = cell(numVars, 1);
    for ii = 1:numVars
        varNames{ii} = vars(ii).name;
        varValues{ii} = evalin(where, vars(ii).name);
    end
    batchHelper.filterAndSetCallerWorkspace(varNames, varValues);
end

% Configuration
if isempty(batchHelper.Configuration)
    batchHelper.Configuration = pch.getDefaultName();
end

try
    % Get the scheduler to use on this job
    scheduler = pch.buildScheduler(batchHelper.Configuration);
    % Actually run batch on the scheduler
    job = batchHelper.doBatch(scheduler);
catch err
    switch err.identifier
        case 'parallel:settings:DisallowedThreadsProfile'
            % Provide clearer error that 'batch' is not supported.
            error(message('parallel:settings:DisallowedThreadsProfile', mfilename));
        otherwise
            % Make all errors appear from batch
            throw(err);
    end
end
