=head1 NAME

  CFX5::Simulation::ConfigSettings - Configuration settings for CFX5::Simulation::Configuration

=head1 SYNOPSIS

This object is inherited by CFX5::Simulation::Configuration

=head1 DESCRIPTION

$Id: ConfigSettings.pm 136309 2016-06-23 09:39:20Z cjstaple $

=cut

package CFX5::Simulation::ConfigSettings;

use strict;
use base 'CFX5::Simulation::SettingsBase';

use strict;
use CFX5::Output qw(:all error_text);
use CFX5::Output::Utils 'wraptext';
use CFX5::FileTests;
use CFX5::FileStat;

use CFX5::PathManip qw(dirname basename fullpath join_file normalize_filename);
use CFX5::FileOps qw(select_execarch);

use CFX5::Config qw(setconf %Config export_conf getconf_default getconf
                    append_to_libpath);

use CFX5::CCLI;
use CFX5::Files::Partition;
use CFX5::PkgLoader qw(cfx5_require);
use CFX5::ResInfo ; 

use Carp;

=head2 SETTINGS

=cut
sub _ok_batch {
  1;
}
sub _def_batch {
  0;
}

sub _ok_cclupdatelog {
  1;
}
sub _def_cclupdatelog {
  my $self = shift;
  my $cclupdatelogobj = CFX5::Output::CCLUpdateLog->new();
  $self->{settings}{cclupdatelog} = $cclupdatelogobj;
  return $cclupdatelogobj;
}

=head3 RUN NAMING

=head4 name

Name for the run, eg "static_mixer".

Set to: a filename.  The directory should exist, if specified;
normally, one would give a path without a directory.

Default: Full path to the current directory with the basename of the
deffile with the .def knocked off, or undef if deffile doesn\'t end in
.def anyway.

=cut

sub _ok_name {
  my $self=shift;
  my $name=shift;
  return 0 unless defined $name;
  1;
}
sub _def_name {
  my $self=shift;
  my $deffile=$self->deffile();
  return undef unless defined $deffile;
  if($deffile =~ m/\.res$/i){
    $deffile =~ s/(_\d\d\d)?\.[^\.]+$//i;
  } else {
    $deffile =~ s/\.[^\.]+$//i;
  }
  fullpath(basename($deffile));
}

=head4 respect_suffixhist

Set to: TRUE to consider the names of files generated by runs in the entire
restart chain, rather than just files in the current working directory.

Default: FALSE

=cut

sub _ok_respect_suffixhist {
  return 0 unless defined $_[1];
  1;
}
sub _def_respect_suffixhist {
  0;
}

=head4 fullname

The full name of a run, including the numbered suffix, eg
"static_mixer_004".  This function should not normally be used, but is
used when the solver is called by the solver manager to make sure
everything is consistent.

Set to: a filename.  If files for this name exist, this will not be
signalled until one tries to prepare the run.

Default: none, but see C<ChooseName>.

=cut

sub _ok_fullname {
  my $self=shift;
  my $name=shift;
  return 0 unless defined $name;
  1;
}
sub _def_fullname { undef; }

sub _ok_configname {
  my $self=shift;
  my $name=shift;
  return 0 unless defined $name;
  1;
}
sub _def_configname {
  my $self=shift;
  my $name=shift;
  
  if (defined $name) {
    internal_error("Setting a configuration name is not yet supported");
    return undef ;
  }
  
  my $cclapi=$self->ccl_api_obj() ;
  unless (defined $cclapi) {
    _error "configname: Could not connect to CCL" ;
  }
  
  my @configList ;
  $cclapi->getObjectsByType("CONFIGURATION",\@configList) ;
  
  my $nConfigs = scalar @configList ;
  
  # All OK; we've got exactly one config defined
  if ($nConfigs == 1) {
    $configList[0] =~ s/(.*)CONFIGURATION:// ;
    return $configList[0] ; 
  }
  # Can't have more than one config
  elsif ($nConfigs > 1) {
    _error("The specified Solver Input File appears to be a multi-configuration case.  Please use the -mdef option instead of the -def option.");
    return undef;
  }
  # No config name in CCL
  else {
    return undef; 
  }

  # Should never get here...
  1;   
}

=head3 BASIC INPUT AND OUTPUT FILES (SERIAL SOLVER)

=head4 deffile

Input definition file.

Set to: a filename.  The file must exist when this is set.

Default: none.

=cut

sub _ok_mdeffile {
  my $self=shift;
  my $file=shift;

  return 0 unless CFX5::FileTests::datafile($file, "ANSYS CFX Solver Input File $file");
  1;
}
sub _def_mdeffile { undef; }
  

sub _ok_deffile {
  my $self=shift;
  my $file=shift;

  return 0 unless CFX5::FileTests::datafile($file, "ANSYS CFX Solver Input File $file");
  1;
}
sub _def_deffile { undef; }

=head4 max_num_its

Maximum Number of Iterations

=cut

sub _def_max_num_its { 0; }
sub _ok_max_num_its { 1; }


=head4 time_duration

Maximum Time for run

=cut

sub _def_time_duration { 0; }
sub _ok_time_duration { 1; }

=head4 num_timesteps_per_run

Number of timesteps for run

=cut

sub _def_num_timesteps_per_run { 0; }
sub _ok_num_timesteps_per_run { 1; }

=head4 time_per_run

Elapsed time per run

=cut

sub _def_time_per_run { 0; }
sub _ok_time_per_run { 1; }

=head4 outfile

The name for the output file.

Set to: a filename which is OK to write to.

Default: I<fullname>.out

=cut

sub _ok_outfile {
  my $self=shift;
  my $file=shift;
  CFX5::FileTests::datafile_w($file);
}
sub _def_outfile {
  my $self=shift;
  (defined $self->fullname() ? $self->fullname().'.out' : undef);
}

=head4 outobj

A C<CFX5::Output::File> object (or derivative?) representing the
output file.  This setting is set by C<StartOutFile>, and unset by
C<EndOutFile>; it should never be written by any other routine.

Set to: An object of type C<CFX5::Output::File>

Default: undef

=cut

sub _ok_outobj {
  1;
}
sub _def_outobj {
  undef;
}

=head4 outobjccl

A C<CFX5::Output::CCL> object (or derivative?) representing the
channel to the solver manager.  This setting is set by
C<StartOutFile>, and unset by C<EndOutFile>; it should never be
written by any other routine.

Set to: An object of type C<CFX5::Output::CCL>

Default: undef

=cut

sub _ok_outobjccl {
  1;
}
sub _def_outobjccl {
  undef;
}

=head4 ownoutfile

TRUE if we "own" the output file.  FALSE if it's there for our use,
but we shouldn't (eg) write termination messages into it.

Set to: boolean

Default: TRUE

=cut

sub _ok_ownoutfile {
  1;
}
sub _def_ownoutfile {
  my $self=shift;
  if ($self->parent()->indirect_startup()) {
    return 0;
  } else {
    return 1;
  }
}

=head4 resfile

The name for the results file to write.

Set to: a filename we can write to.

Default: I<fullname>.res

=cut

sub _ok_resfile {
  my $self=shift;
  my $file=shift;
  CFX5::FileTests::datafile_w($file);
}
sub _def_resfile {
  my $self=shift;
  # TODO: we default to a .cfx extension, unless the input file is
  # .def or .res.  Is this sensible?  It seems the most future-proof
  # to me ...
  my $ext='.res';
  if (defined $self->deffile() and $self->deffile() =~ m!\.(cfx)$!i) {
    $ext='.cfx';
  }
  (defined $self->fullname() ? $self->fullname().$ext : undef);
}
sub _def_resfile_ccl {
  my $self = shift;
  $self->resfile();
}

=head4 save_resfile

Returns TRUE if the results file is to be saved by checking if the run
is not a partitioning only run and results file output is not disabled 
 in CCL. If the relevant CCL doesn't exist, we assume a results file is expected.

=cut

sub _ok_save_resfile {
  return 0 unless defined $_[1];
  1;
}

sub _def_save_resfile {
  my $self=shift;
  return 0 if ($self->run_mode() eq 'Partitioner Only');
  my $ccl_param = '/FLOW/OUTPUT CONTROL/RESULTS/Option';
  return 1 unless $self->have_ccl_parm($ccl_param);
  my $res_output_option = $self->read_ccl_parm($ccl_param);
  return $res_output_option ne 'None';
}

=head4 resfile_err

The name for the results file to write if a problem is encountered,
but we still want to save some results.

Set to: a filename we can write to.

Default: I<resfile>.err

=cut

sub _ok_resfile_err {
  my $self=shift;
  my $file=shift;
  CFX5::FileTests::datafile_w($file);
}
sub _def_resfile_err {
  my $self=shift;
  (defined $self->resfile() ? $self->resfile().'.err' : undef);
}

=head4 resfile_from

This is an internal setting.  It is usually 'undef', but can also be
set to the name of the most up-to-date results file available, if it
is not the standard 'res' file in the working directory.  This is used
by the adaption code to ensure that some results file can be output if
stop is pressed during an adaption run.

The setting is only used when the 'res' file does not exist in the
working directory anyway.

Set to: C<undef>, or a filename.

Default: C<undef>

=cut

sub _ok_resfile_from {
  my $self=shift;
  my $file=shift;
  1;
}
sub _def_resfile_from {
  my $self=shift;
  undef;
}

=head4 rundir

Directory to work in.

Set to: directory we can create/write to.

Default: I<fullname>.dir

=cut

sub _ok_rundir {
  my $self=shift;
  my $file=shift;

  # if we've already got an object pointing to this directory, we
  # won't change it by trying to fix this value, so give a warning if
  # somebody tries to do this.  Remember, always run with -w ...
  if ($^W) {
    if (exists $self->{settings}->{rundir_obj}) {
      warn "Resetting rundir after rundir_obj was created, job will probably be\ninconsistent";
    }
  }
  1;
}
sub _def_rundir {
  my $self=shift;
  (defined $self->fullname() ? $self->fullname().'.dir' : undef);
}

=head4 rundir_obj

This is a L<CFX5::WorkDir> object representing the working directory.

Set to: don't, under normal circumstances.  Set C<rundir> and then use
it.

Default: created using the setting of C<rundir> on first access.

=cut 

sub _ok_rundir_obj {
  my $self=shift;
  my $obj=shift;

  unless (!defined($obj) 
	  or (ref($obj) and $obj->isa('CFX5::WorkDir'))) {
    internal_error("Argument to rundir_obj not a CFX5::WorkDir object");
    return 0;
  }
}
sub _def_rundir_obj {
  my $self=shift;

  $self->_DoRunDir(1)
    or return undef;

  # the above sets this, here we just return it
  $self->{settings}->{rundir_obj};
}

=head3 MISCELLANEOUS SOLVER FILES AND SETTINGS


=head4 default_executable

Return the correct executable name for the host system

=cut

sub default_executable {
  my $self = shift;

  my $defaultexe = 'solver-mpi.exe';
  return $defaultexe;
}

=head4 global_exec_type

Set to: global attributes

Default: from CCL

There are three parts to the EXECUTABLE SELECTION object in CCL.
Here, we set the C<Double Precision> and C<Use 64 Bit> parameters if
we get either of these attributes, and set the C<Attribute List> to
the remainder of the non-default attributes, as this should at least
be clear.

On reading, we read all the Attribute List, and then apply C<Double
Precision> and C<Use 64 Bit> if they are present, whether they are
C<Yes> or C<No>.  If they are not present at all, they are not used.

=cut

sub global_exec_type {
  my $self=shift;

  my $root='/SIMULATION CONTROL/EXECUTION CONTROL/EXECUTABLE SELECTION';

  cfx5_require('CFX5::CCLI::ExecutableSelection');
  if (@_) {
    return CFX5::CCLI::ExecutableSelection->set($self, $root, undef, @_);
  }

  my $exec_type = CFX5::CCLI::ExecutableSelection->get($self, $root);

  return $exec_type;
}

=head5 global_exec_set_attr

  $job->global_exec_set_attr('double');

Preserving all other settings, sets the named attribute(s).

=cut

sub global_exec_set_attr {
  my $self=shift;
  my @attrs=@_;

  my $current = defined $self->global_exec_type() ? $self->global_exec_type() : '';  
  my $attrs = CFX5::Attr->new($current);
  $attrs->merge_attr_list(@attrs);
  $self->global_exec_type($attrs->get_attr_list());
  1;
}

=head4 solver_exec_type

Set to: attributes for solver executable.

Default: from CCL

There are three parts to the EXECUTABLE SELECTION object in CCL.
Here, we set the C<Double Precision> and C<Use 64 Bit> parameters if
we get either of these attributes, and set the C<Attribute List> to
the remainder of the non-default attributes, as this should at least
be clear.

On reading, we read all the Attribute List, and then apply C<Double
Precision> and C<Use 64 Bit> if they are present, whether they are
C<Yes> or C<No>.  If they are not present at all, they are not used.

=cut

sub solver_exec_type {
  my $self=shift;

  my $root='/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/EXECUTABLE SELECTION';
  my $groot='/SIMULATION CONTROL/EXECUTION CONTROL/EXECUTABLE SELECTION';

  cfx5_require('CFX5::CCLI::ExecutableSelection');
  if (@_) {
    return CFX5::CCLI::ExecutableSelection->set($self, $root, $groot, @_);
  }

  return CFX5::CCLI::ExecutableSelection->get($self, $root, $groot);
}

=head5 solver_exec_set_attr

  $job->solver_exec_set_attr('double');

Preserving all other settings, sets the named attribute(s).

=cut

sub solver_exec_set_attr {
  my $self=shift;
  my @attrs=@_;

  my $current = defined $self->solver_exec_type() ? $self->solver_exec_type() : '';  
  my $attrs = CFX5::Attr->new($current);
  $attrs->merge_attr_list(@attrs);
  $self->solver_exec_type($attrs->get_attr_list());
  1;
}

=head5 solver_exec

  $job->solver_exec('linux-amd64', '/tmp/mysolver.exe');

  my $exe=$job->solver_exec('linux-amd64');

  my $exe=$job->solver_exec();

Set/get a custom solver exe for a specific platform.
If platform is omitted the defaults to the current platform.
Returns undef or the path to the exe.

=cut  

sub solver_exec {
  my $self=shift;
  my $arch=shift;
  my $exe=shift;

  return $Config{CFX_SOLVER_EXEC} if defined $Config{CFX_SOLVER_EXEC};
  if(not defined $arch){
    return $self->{solver_exes}->{$Config{CFX5OS}};
  }
  elsif(not defined $exe){
    return $self->{solver_exes}->{$arch};
  }
  else {
    # Can't check for existance, could be a remote host
    $self->{solver_exes}->{$arch}=fullpath($exe);
  }
}

=head4 partitioner_exec_arch

Set to: default architecture for partitioner executable

Default: calculated from the currently available partitioners and the other
partitioner_exec_*, ppe_mode etc settings.

=cut

{
  my %cache;
  sub _def_partitioner_exec_arch {
    my $self=shift;

    return $Config{CFX_PARTITIONER_EXEC_ARCH} 
      if defined $Config{CFX_PARTITIONER_EXEC_ARCH};

    # this is used by the regression tests
    if (defined $Config{CFX5ARCH}) {
      return $Config{CFX5ARCH};
    }

    my $defaultexe = $self->default_executable();
    my $current=defined $self->partitioner_exec_type() ? $self->partitioner_exec_type() : $Config{CFX5_ARCH_TYPE};

    my @args=($defaultexe,
              $Config{CFX5BINDIR},
              $Config{CFX5_HOST_ARCH},
              $current);
    my $confkey=join("!",@args);
    my $arch;
    if (exists $cache{$confkey}) {
      $arch=$cache{$confkey};
      if (defined $arch) {
        _debug("Found cached partitioner arch $arch for $confkey");
      } else {
        _debug("Cached partitioner arch is undef for $confkey");
      }
    } else {
      $arch=select_execarch(@args);
      # store for Ron
      $cache{$confkey}=$arch;
      if (defined $arch) {
        _debug("Found partitioner arch $arch for $confkey");
      }
    }

    unless (defined $arch) {
      # couldn't find anything
      _debug("Unable to find partitioner arch for $confkey");
      return undef;
    }

    $arch;
  }
}
sub _ok_partitioner_exec_arch {
  1;
}

=head4 partitioner_exec_type

Set to: attributes for partitioner executable.

Default: from CCL

There are three parts to the EXECUTABLE SELECTION object in CCL.
Here, we set the C<Double Precision> and C<Use 64 Bit> parameters if
we get either of these attributes, and set the C<Attribute List> to
the remainder of the non-default attributes, as this should at least
be clear.

On reading, we read all the Attribute List, and then apply C<Double
Precision> and C<Use 64 Bit> if they are present, whether they are
C<Yes> or C<No>.  If they are not present at all, they are not used.

=cut

sub partitioner_exec_type {
  my $self=shift;

  my $root='/SIMULATION CONTROL/EXECUTION CONTROL/PARTITIONER STEP CONTROL/EXECUTABLE SELECTION';
  my $groot='/SIMULATION CONTROL/EXECUTION CONTROL/EXECUTABLE SELECTION';

  cfx5_require('CFX5::CCLI::ExecutableSelection');
  if (@_) {
    return CFX5::CCLI::ExecutableSelection->set($self, $root, $groot, @_);
  }
  return CFX5::CCLI::ExecutableSelection->get($self, $root, $groot);
}

=head5 partitioner_exec_set_attr

  $job->partitioner_exec_set_attr('double');

Preserving all other settings, sets the named attribute(s).

=cut

sub partitioner_exec_set_attr {
  my $self=shift;
  my @attrs=@_;

  my $current = defined $self->partitioner_exec_type() ? $self->partitioner_exec_type() : '';  
  my $attrs = CFX5::Attr->new($current);
  $attrs->merge_attr_list(@attrs);
  $self->partitioner_exec_type($attrs->get_attr_list());
  1;
}

=head4 partitioner_exec

This setting holds the partitioner executable to use.  It is a setting, as
it depends on various other settings on this object for its value.
(Since the default could take a while to calculate, however, the
calculation is cached).

Set to: partitioner executable path

Default: $Config{CFX_PARTITIONER_EXEC} if set, otherwise searches for the
executable using the normal rules.

=cut

sub _def_partitioner_exec {
  my $self=shift;

  return $Config{CFX_PARTITIONER_EXEC} if defined $Config{CFX_PARTITIONER_EXEC};

  # no env var, need to find it

  # the architecture
  my $arch=$self->partitioner_exec_arch();

  unless (defined $arch) {
    # oops
    return undef;
  }

  my $defaultexe = $self->default_executable();

  my @p=($Config{CFX5BINDIR},$arch);
  push @p, $self->partitioner_exec_type() if $self->partitioner_exec_type();
  push @p, $defaultexe;
  my $partitioner=join_file(@p);

  # for future reference
  #$Config{CFX_PARTITIONER_EXEC}=$partitioner;
  #$Config{CFX_PARTITIONER_EXEC_ARCH}=$arch;

  return $partitioner;
}
sub _ok_partitioner_exec {
  CFX5::FileTests::execfile($_[1], "ANSYS CFX Partitioner executable $_[1]");
}

=head4 partitioner_exec_default

This call returns the default partitioner executable to use, based on the
various other settings, without checking for user overrides.  It is
for use to check whether the partitioner executable is still the default.

=cut

sub partitioner_exec_default {
  my $self=shift;

  # the architecture
  my $arch=$self->partitioner_exec_arch();
  return undef unless defined $arch;

  my $defaultexe = $self->default_executable();

  my @p=($Config{CFX5BINDIR},$arch);
  push @p, $self->partitioner_exec_type() if $self->partitioner_exec_type();
  push @p, $defaultexe;
  my $partitioner=join_file(@p);

  return $partitioner;
}

=head4 partitioner_exec_name

  print "Using the", $self->partitioner_exec_name();

  Using the ANSYS CFX "double" partitioner optimised for the winnt architecture.

Sounds silly, but it returns a descriptive name for the current partitioner
executable.  This means we can use it in lots of messages.

=cut

sub partitioner_exec_name {
  my $self=shift;

  my $str;
  my ($now,$def)=($self->partitioner_exec(), $self->partitioner_exec_default());
  if (!defined $now) {
    # we couldn't find one, so give a generic type of what we were
    # looking for
    my $attr=$self->partitioner_exec_type();
    $str="ANSYS CFX";
    if (defined $attr and $attr ne '') {
      $str .= " \"$attr\"";
    }
    $str .= " Partitioner";
  } elsif ($now eq $def) {
    my ($attr,$arch)=($self->partitioner_exec_type(),$self->partitioner_exec_arch());
    $str="ANSYS CFX";
    if (defined $attr and $attr ne '') {
      $str .= " \"$attr\"";
    }
    $str .= " Partitioner optimised for the $arch architecture";
  } else {
    $str="ANSYS CFX Partitioner";
  }

  $str;
}

=head4 interpolator_exec_type

Set to: attributes for solver-based interpolator executable.

Default: from CCL

There are three parts to the EXECUTABLE SELECTION object in CCL.
Here, we set the C<Double Precision> and C<Use 64 Bit> parameters if
we get either of these attributes, and set the C<Attribute List> to
the remainder of the non-default attributes, as this should at least
be clear.

On reading, we read all the Attribute List, and then apply C<Double
Precision> and C<Use 64 Bit> if they are present, whether they are
C<Yes> or C<No>.  If they are not present at all, they are not used.

=cut

sub interpolator_exec_type {
  my $self=shift;

  my $root='/SIMULATION CONTROL/EXECUTION CONTROL/INTERPOLATOR STEP CONTROL/EXECUTABLE SELECTION';
  my $groot='/SIMULATION CONTROL/EXECUTION CONTROL/EXECUTABLE SELECTION';

  cfx5_require('CFX5::CCLI::ExecutableSelection');
  if (@_) {
    return CFX5::CCLI::ExecutableSelection->set($self, $root, $groot, @_);
  }
  return CFX5::CCLI::ExecutableSelection->get($self, $root, $groot);
}

=head5 interpolator_exec_set_attr

  $job->interpolator_exec_set_attr('double');

Preserving all other settings, sets the named attribute(s).

=cut

sub interpolator_exec_set_attr {
  my $self=shift;
  my @attrs=@_;

  my $current = defined $self->interpolator_exec_type() ? $self->interpolator_exec_type() : '';  
  my $attrs = CFX5::Attr->new($current);
  $attrs->merge_attr_list(@attrs);
  $self->interpolator_exec_type($attrs->get_attr_list());
  1;
}

=head4 interpolator_exec_arch

Set to: default architecture for interpolator executable

Default: calculated from the currently available interpolators and the other
interpolator_exec_*, ppe_mode etc settings.

=cut

{
  my %cache;
  sub _def_interpolator_exec_arch {
    my $self=shift;

    return $Config{CFX_SOLVER_INTERPOLATOR_EXEC_ARCH} 
      if defined $Config{CFX_SOLVER_INTERPOLATOR_EXEC_ARCH};

    # this is used by the regression tests
    if (defined $Config{CFX5ARCH}) {
      return $Config{CFX5ARCH};
    }

    my $defaultexe = $self->default_executable();
    my $current=defined $self->interpolator_exec_type() ? $self->interpolator_exec_type() : '';

    my @args=($defaultexe,
              $Config{CFX5BINDIR},
              $Config{CFX5_HOST_ARCH},
              $current);
    my $confkey=join("!",@args);
    my $arch;
    if (exists $cache{$confkey}) {
      $arch=$cache{$confkey};
      if (defined $arch) {
        _debug("Found cached interpolator arch $arch for $confkey");
      } else {
        _debug("Cached interpolator arch is undef for $confkey");
      }
    } else {
      $arch=select_execarch(@args);
      # store for Ron
      $cache{$confkey}=$arch;
      if (defined $arch) {
        _debug("Found interpolator arch $arch for $confkey");
      }
    }

    unless (defined $arch) {
      # couldn't find anything
      _debug("Unable to find interpolator arch for $confkey");
      return undef;
    }

    $arch;
  }
}
sub _ok_interpolator_exec_arch {
  1;
}



=head4 interpolator_exec

This setting holds the interpolator executable to use.  It is a setting, as
it depends on various other settings on this object for its value.
(Since the default could take a while to calculate, however, the
calculation is cached).

Set to: interpolator executable path

Default: $Config{CFX_SOLVER_INTERP_EXEC} if set, otherwise searches for the
executable using the normal rules.

=cut

sub _def_interpolator_exec {
  my $self=shift;

  return $Config{CFX_SOLVER_INTERP_EXEC} if defined $Config{CFX_SOLVER_INTERP_EXEC};

  # no env var, need to find it

  # the architecture
  my $arch=$self->interpolator_exec_arch();

  unless (defined $arch) {
    # oops
    return undef;
  }

  my $defaultexe = $self->default_executable();

  my @p=($Config{CFX5BINDIR},$arch);
  push @p, $self->interpolator_exec_type() if $self->interpolator_exec_type();
  push @p, $defaultexe;
  my $interp=join_file(@p);

  return $interp;
}
sub _ok_interpolator_exec {
  CFX5::FileTests::execfile($_[1], "ANSYS CFX Interpolator executable $_[1]");
}


=head4 ccl2flow_exec_arch

Set to: default architecture for ccl2flow executable

Default: calculated from the currently available ccl2flows and the other
ccl2flow_exec_*, ppe_mode etc settings.

=cut

{
  my %cache;
  sub _def_ccl2flow_exec_arch {
    my $self=shift;

    return $Config{CFX_SOLVER_CCL2FLOW_EXEC_ARCH} 
      if defined $Config{CFX_SOLVER_CCL2FLOW_EXEC_ARCH};

    # this is used by the regression tests
    if (defined $Config{CFX5ARCH}) {
      return $Config{CFX5ARCH};
    }

    my $defaultexe = 'ccl2flow.exe';
    my $current=defined $self->ccl2flow_exec_type() ? $self->ccl2flow_exec_type() : '';

    my @args=($defaultexe,
              $Config{CFX5BINDIR},
              $Config{CFX5_HOST_ARCH},
              $current);
    my $confkey=join("!",@args);
    my $arch;
    if (exists $cache{$confkey}) {
      $arch=$cache{$confkey};
      if (defined $arch) {
        _debug("Found cached ccl2flow arch $arch for $confkey");
      } else {
        _debug("Cached ccl2flow arch is undef for $confkey");
      }
    } else {
      $arch=select_execarch(@args);
      # store for Ron
      $cache{$confkey}=$arch;
      if (defined $arch) {
        _debug("Found ccl2flow arch $arch for $confkey");
      }
    }

    unless (defined $arch) {
      # couldn't find anything
      _debug("Unable to find ccl2flow arch for $confkey");
      return undef;
    }

    $arch;
  }
}
sub _ok_ccl2flow_exec_arch {
  1;
}

=head4 ccl2flow_exec_type

Set to: attributes for solver-based ccl2flow executable.

Default: from CCL

There are three parts to the EXECUTABLE SELECTION object in CCL.
Here, we set the C<Double Precision> and C<Use 64 Bit> parameters if
we get either of these attributes, and set the C<Attribute List> to
the remainder of the non-default attributes, as this should at least
be clear.

On reading, we read all the Attribute List, and then apply C<Double
Precision> and C<Use 64 Bit> if they are present, whether they are
C<Yes> or C<No>.  If they are not present at all, they are not used.

=cut

sub ccl2flow_exec_type {
  my $self=shift;

  my $root='/SIMULATION CONTROL/EXECUTION CONTROL/CCL2FLOW STEP CONTROL/EXECUTABLE SELECTION';
  my $groot='/SIMULATION CONTROL/EXECUTION CONTROL/EXECUTABLE SELECTION';

  cfx5_require('CFX5::CCLI::ExecutableSelection');
  my $exec_type;
  if (@_) {
    $exec_type = CFX5::CCLI::ExecutableSelection->set($self, $root, $groot, @_);
  } else {
    $exec_type = CFX5::CCLI::ExecutableSelection->get($self, $root, $groot);
  }

  # int64 -> not applied to ccl2flow
  # ifort -> only applied to ccl2flow if double
  if (defined $exec_type) {
    my $attrs = CFX5::Attr->new($exec_type);
    $attrs->clear_attr('int64');
    $attrs->set_attr('archfort') unless $attrs->get_attr('double');
    $exec_type = $attrs->partial_attr_list();
  }

  return $exec_type;
}

=head5 ccl2flow_exec_set_attr

  $job->ccl2flow_exec_set_attr('double');

Preserving all other settings, sets the named attribute(s).

=cut

sub ccl2flow_exec_set_attr {
  my $self=shift;
  my @attrs=@_;

  my $current = defined $self->ccl2flow_exec_type() ? $self->ccl2flow_exec_type() : '';  
  my $attrs = CFX5::Attr->new($current);
  $attrs->merge_attr_list(@attrs);
  $self->ccl2flow_exec_type($attrs->get_attr_list());
  1;
}

=head4 ccl2flow_exec

This setting holds the ccl2flow executable to use.  It is a setting, as
it depends on various other settings on this object for its value.
(Since the default could take a while to calculate, however, the
calculation is cached).

Set to: ccl2flow executable path

Default: $Config{CFX5_CCL2FLOW_EXEC} if set, otherwise searches for the
executable using the normal rules.

=cut

sub _def_ccl2flow_exec {
  my $self=shift;

  return $Config{CFX5_CCL2FLOW_EXEC} if defined $Config{CFX5_CCL2FLOW_EXEC};

  # no env var, need to find it

  # the architecture
  my $arch=$self->ccl2flow_exec_arch();

  unless (defined $arch) {
    # oops
    return undef;
  }

  my $defaultexe = 'ccl2flow.exe';
  my @p=($Config{CFX5BINDIR},$arch);
  push @p, $self->ccl2flow_exec_type() if $self->ccl2flow_exec_type();
  push @p, $defaultexe;
  my $exec = join_file(@p);

  return $exec;
}

sub _ok_ccl2flow_exec {
  CFX5::FileTests::execfile($_[1], "ANSYS CFX ccl2flow executable $_[1]");
}


=head4 cclsetup_exec_arch

Set to: default architecture for cclsetup executable

Default: calculated from the currently available cclsetups and the other
cclsetup_exec_*, ppe_mode etc settings.

=cut

{
  my %cache;
  sub _def_cclsetup_exec_arch {
    my $self=shift;

    return $Config{CFX_SOLVER_CCLSETUP_EXEC_ARCH} 
      if defined $Config{CFX_SOLVER_CCLSETUP_EXEC_ARCH};

    # this is used by the regression tests
    if (defined $Config{CFX5ARCH}) {
      return $Config{CFX5ARCH};
    }

    my $defaultexe = 'cclsetup.exe';
    my $current=defined $self->cclsetup_exec_type() ? $self->cclsetup_exec_type() : '';

    my @args=($defaultexe,
              $Config{CFX5BINDIR},
              $Config{CFX5_HOST_ARCH},
              $current);
    my $confkey=join("!",@args);
    my $arch;
    if (exists $cache{$confkey}) {
      $arch=$cache{$confkey};
      if (defined $arch) {
        _debug("Found cached cclsetup arch $arch for $confkey");
      } else {
        _debug("Cached cclsetup arch is undef for $confkey");
      }
    } else {
      $arch=select_execarch(@args);
      # store for Ron
      $cache{$confkey}=$arch;
      if (defined $arch) {
        _debug("Found cclsetup arch $arch for $confkey");
      }
    }

    unless (defined $arch) {
      # couldn't find anything
      _debug("Unable to find cclsetup arch for $confkey");
      return undef;
    }

    $arch;
  }
}
sub _ok_cclsetup_exec_arch {
  1;
}

=head4 cclsetup_exec_type

Set to: attributes for solver-based cclsetup executable.

Default: from CCL

There are three parts to the EXECUTABLE SELECTION object in CCL.
Here, we set the C<Double Precision> and C<Use 64 Bit> parameters if
we get either of these attributes, and set the C<Attribute List> to
the remainder of the non-default attributes, as this should at least
be clear.

On reading, we read all the Attribute List, and then apply C<Double
Precision> and C<Use 64 Bit> if they are present, whether they are
C<Yes> or C<No>.  If they are not present at all, they are not used.

=cut

sub cclsetup_exec_type {
  my $self=shift;

  my $root='/SIMULATION CONTROL/EXECUTION CONTROL/CCLSETUP STEP CONTROL/EXECUTABLE SELECTION';
  my $groot='/SIMULATION CONTROL/EXECUTION CONTROL/EXECUTABLE SELECTION';

  cfx5_require('CFX5::CCLI::ExecutableSelection');
  my $exec_type;
  if (@_) {
    $exec_type = CFX5::CCLI::ExecutableSelection->set($self, $root, $groot, @_);
  } else {
    $exec_type = CFX5::CCLI::ExecutableSelection->get($self, $root, $groot);
  }

  # int64 -> not applied to cclsetup
  # ifort -> only applied to cclsetup if double
  if (defined $exec_type) {
    my $attrs = CFX5::Attr->new($exec_type);
    $attrs->clear_attr('int64');
    $attrs->set_attr('archfort') unless $attrs->get_attr('double');
    $exec_type = $attrs->partial_attr_list();
  }
  
  return $exec_type;
}

=head5 cclsetup_exec_set_attr

  $job->cclsetup_exec_set_attr('double');

Preserving all other settings, sets the named attribute(s).

=cut

sub cclsetup_exec_set_attr {
  my $self=shift;
  my @attrs=@_;

  my $current = defined $self->cclsetup_exec_type() ? $self->cclsetup_exec_type() : '';  
  my $attrs = CFX5::Attr->new($current);
  $attrs->merge_attr_list(@attrs);
  $self->cclsetup_exec_type($attrs->get_attr_list());
  1;
}

=head4 cclsetup_exec

This setting holds the cclsetup executable to use.  It is a setting, as
it depends on various other settings on this object for its value.
(Since the default could take a while to calculate, however, the
calculation is cached).

Set to: cclsetup executable path

Default: $Config{CFX5_CCLSETUP_EXEC} if set, otherwise searches for the
executable using the normal rules.

=cut

sub _def_cclsetup_exec {
  my $self=shift;

  return $Config{CFX5_CCLSETUP_EXEC} if defined $Config{CFX5_CCLSETUP_EXEC};

  # no env var, need to find it

  # the architecture
  my $arch=$self->cclsetup_exec_arch();

  unless (defined $arch) {
    # oops
    return undef;
  }

  my $defaultexe = 'cclsetup.exe';
  my @p=($Config{CFX5BINDIR},$arch);
  push @p, $self->cclsetup_exec_type() if $self->cclsetup_exec_type();
  push @p, $defaultexe;
  my $exec = join_file(@p);

  return $exec;
}

sub _ok_cclsetup_exec {
  CFX5::FileTests::execfile($_[1], "ANSYS CFX cclsetup executable $_[1]");
}

=head4 run_mode

Set to: 'Full' or 'Partitioner Only'

Default: 'Full'

=cut

sub _ok_run_mode {
  my %ok=( Full => 1,
           'Partitioner Only' => 1,
   	   );
  return 0 unless $ok{$_[1]};
  1;
}
sub _def_run_mode {
  'Full';
}

=head4 jobfile

The name for the job file to write, if any (see C<save_jobfile>).

Set to: a filename we can write to.

Default: I<fullname>.job

=cut

sub _ok_jobfile {
  my $self=shift;
  my $file=shift;
  CFX5::FileTests::datafile_w($file);
}
sub _def_jobfile {
  my $self=shift;
  (defined $self->fullname() ? $self->fullname().'.job' : undef);
}

=head4 pjobfile

The name for the partitioner job file to write, if any (see C<save_pjobfile>).

Set to: a filename we can write to.

Default: I<fullname>.job

=cut

sub _ok_pjobfile {
  my $self=shift;
  my $file=shift;
  CFX5::FileTests::datafile_w($file);
}
sub _def_pjobfile {
  my $self=shift;
  (defined $self->fullname() ? $self->fullname().'.pjob' : undef);
}

=head4 save_jobfile

Set to: 1 if we want to keep the job file from the solver run, 0 else.

Default: 0

=cut

sub save_jobfile {
  my $self = shift;
  my $option = shift;
  my $ccl_param = '/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/Save Job File';

  if (defined $option) {
    $self->set_ccl_parm($ccl_param => $option ? 'Yes' : 'No');
  } else {
    $option = CFX5::CCLI::Defaulted->interpret_logl($self->read_ccl_parm($ccl_param));
  }

  return $option;
}

=head4 save_pjobfile

Set to: 1 if we want to keep the partitioner job file from the partitioner run, 0 else.

Default: 0

=cut

sub _ok_save_pjobfile {
  return 0 unless defined $_[1];
  1;
}
sub _def_save_pjobfile {
  0;
}

=head4 jobfile_53

The name for the old-style job file to write, if any (see
C<save_jobfile_53>).

Set to: a filename we can write to.

Default: I<fullname>.job53

=cut

sub _ok_jobfile_53 {
  my $self=shift;
  my $file=shift;
  CFX5::FileTests::datafile_w($file);
}
sub _def_jobfile_53 {
  my $self=shift;
  (defined $self->fullname() ? $self->fullname().'.job53' : undef);
}

=head4 save_jobfile_53

Set to: 1 if we want to keep the old-style job file from the solver
run, 0 else.

Default: 0

=cut

sub _ok_save_jobfile_53 {
  return 0 unless defined $_[1];
  1;
}
sub _def_save_jobfile_53 {
  0;
}

=head4 solver_priority

Set to: If set, this gives the process priority for the various solver
processes (corresponding to the -pri argument to the solver).  This
should be "Low", "Standard" or "High".

Default: none.

=cut

sub _ok_solver_priority {
  my %ok=(
          Idle => 1,
          Low => 1,
          Standard => 1,
          High => 1,
         );
  return 0 unless defined $_[1];
  return 0 unless $ok{$_[1]};
  1;
}
sub _def_solver_priority {
  undef;
}

=head4 memory_factor

Set to: If set, this is the factor by which to increase the solver
memory estimates (corresponding to the -size switch to cfx5solve).

Default: none.

=cut

sub _ok_memory_factor {
  return 0 unless defined $_[1];
  return 0 unless ($_[1] =~ m/^([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$/);
  1;
}
sub _def_memory_factor {
  undef;
}

=head4 cat_factor

Set to: If set, this is the factor by which to increase the solver
mms estimates (corresponding to the -scat switch to cfx5solve).

Default: none.

=cut

sub _ok_cat_factor {
  return 0 unless defined $_[1];
  1;
}
sub _def_cat_factor {
  undef;
}

=head4 mms_factor

Set to: If set, this is the factor by which to increase the solver
mms estimates (corresponding to the -size switch to cfx5solve).

Default: none.

=cut

sub _ok_mms_factor {
  return 0 unless defined $_[1];
  return 0 unless ($_[1] =~ m/^([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$/);
  1;
}
sub _def_mms_factor {
  undef;
}

=head4 memory_factor_part

Set to: If set, this is the factor by which to increase the
partitioner memory estimates (corresponding to the -size-part switch
to cfx5solve).

Default: none.

=cut

sub _ok_memory_factor_part {
  return 0 unless defined $_[1];
  return 0 unless ($_[1] =~ m/^([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$/);
  1;
}
sub _def_memory_factor_part {
  undef;
}

=head4 cat_factor_part

Set to: If set, this is the factor by which to increase the partitioner
mms estimate (corresponding to the -scatpar switch to cfx5solve).

Default: none.

=cut

sub _ok_cat_factor_part {
  return 0 unless defined $_[1];
  1;
}
sub _def_cat_factor_part {
  undef;
}

=head4 mms_factor_part

Set to: If set, this is the factor by which to increase the partitioner
mms estimate (corresponding to the -smmspar switch to cfx5solve).

Default: none.

=cut

sub _ok_mms_factor_part {
  return 0 unless defined $_[1];
  return 0 unless ($_[1] =~ m/^([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$/);
  1;
}
sub _def_mms_factor_part {
  undef;
}

=head4 cat_factor_interp

Set to: If set, this is the factor by which to increase the interpolator
mms estimate (corresponding to the -scatint switch to cfx5solve).

Default: none.

=cut

sub _ok_cat_factor_interp {
  return 0 unless defined $_[1];
  1;
}
sub _def_cat_factor_interp {
  undef;
}

=head4 memory_factor_interp

Set to: If set, this is the factor by which to increase the
interpolator memory estimates (corresponding to the -size-interp switch
to cfx5solve).

Default: none.

=cut

sub _ok_memory_factor_interp {
  return 0 unless defined $_[1];
  return 0 unless ($_[1] =~ m/^([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$/);
  1;
}
sub _def_memory_factor_interp {
  undef;
}

=head4 cat_factor_cclsetup

Set to: If set, this is the factor by which to increase the cclsetup
mms estimate (corresponding to the -scatccl switch to cfx5solve).

Default: none.

=cut

sub _ok_cat_factor_cclsetup {
  return 0 unless defined $_[1];
  1;
}
sub _def_cat_factor_cclsetup {
  undef;
}

=head4 memory_factor_cclsetup

Set to: If set, this is the factor by which to increase the
cclsetup memory estimates (corresponding to the -size-cclsetup switch
to cfx5solve).

Default: none.

=cut

sub _ok_memory_factor_cclsetup {
  return 0 unless defined $_[1];
  return 0 unless ($_[1] =~ m/^([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$/);
  1;
}
sub _def_memory_factor_cclsetup {
  undef;
}

=head4 memory_*

The following additional settings are available for advanced memory
usage settings:

=cut

sub _ok_memory_anyadvanced {
  return 0 unless defined $_[1];
  return 0 unless ($_[1] =~ m/^\d+[KkMmXx]?$/);
  1;
}
sub _def_memory_anyadvanced {
  undef;
}

=over

=item memory_nr

=item memory_ni

=item memory_nd

=item memory_nc

=item memory_nl

=item memory_nr_part

=item memory_ni_part

=item memory_nd_part

=item memory_nc_part

=item memory_nl_part

=item memory_nr_interp

=item memory_ni_interp

=item memory_nd_interp

=item memory_nc_interp

=item memory_nl_interp

=cut

 { 
    for my $f (qw (nr ni nd nc nl)) {
      no strict;
      *{"_ok_memory_$f"}=\&_ok_memory_anyadvanced;
      *{"_def_memory_$f"}=\&_def_memory_anyadvanced;
      *{"_ok_memory_${f}_part"}=\&_ok_memory_anyadvanced;
      *{"_def_memory_${f}_part"}=\&_def_memory_anyadvanced;
      *{"_ok_memory_${f}_interp"}=\&_ok_memory_anyadvanced;
      *{"_def_memory_${f}_interp"}=\&_def_memory_anyadvanced;
      
    }
  }

=back

=head4 outdir

Directory to put transient results files in if the solver produces
any, and also the intermediate files from adaption.

Set to: directory we can create/write to.

Default: I<fullname>

=cut

sub _ok_outdir {
  my $self=shift;
  my $file=shift;
  CFX5::FileTests::datadir_cw($file);
}
sub _def_outdir {
  my $self=shift;
  $self->fullname();
}

=head4 solver_debug_prompt

Start the solver with debug prompt showing process IDs

=cut

sub _def_solver_debug_prompt {
  0 ;
}

sub _ok_solver_debug_prompt {
  1 ;
}

=head4 solver_debug_prompt

Start the solver with user debug prompt showing process IDs

=cut

sub _def_solver_debug_prompt_user {
  0 ;
}

sub _ok_solver_debug_prompt_user {
  1 ;
}

=head4 output_summary_option

Get or set the output summary option controlling the verbosity of
the job information in the solver output file.

If the environment variable CFX_OUTPUT_SUMMARY_OPTION is set, its
value overrides any existing CCL setting.

=cut

sub output_summary_option {
  my $self = shift;
  my $option = shift;
  my $ccl_param = '/FLOW/OUTPUT CONTROL/OUTPUT FILE/Output Summary Option';

  if (defined $option) {
    $self->set_ccl_parm($ccl_param => $option);
  } else {
    $option = $Config{CFX_OUTPUT_SUMMARY_OPTION};

    if (defined $option) {
      $self->set_ccl_parm($ccl_param => $option);
    } else {
      $option = $self->read_ccl_parm($ccl_param);
    }
  }

  return $option;
}

=head3 PARALLEL

In order to support different parallel environments without too much
hassle, we define a general environment, called the PPE, which has a
fairly simple set of states it can be in.  

When a run is set up, it is "started", and "hosts added" to the
environment.  After the run, it is "stopped".  These terms make sense
only for some PPEs, but the stages remain the same for all.

=head4 parallel_mode

Set to: "Serial", "Local Parallel" or "Distributed Parallel"

Default: "Serial"

=cut

sub parallel_mode {
  my $self=shift;
  if (@_) {
    my $ppe=$self->ppe_obj();
    $ppe->set_compat_option(@_);
  }
  if (defined wantarray) {
    internal_error("Can't use the return value of parallel_mode any more");
  }
}

=head4 start_method

Set to: a valid start method

Default: "Serial"

=cut

sub start_method {
  my $self=shift;
  my $ppe=$self->ppe_obj();
  my $sm=$ppe->start_method(@_);
  $sm->name();
}

sub start_method_obj {
  my $self=shift;
  if (@_) {
    internal_error("Cannot set the start method object");
  }
  my $ppe=$self->ppe_obj();
  $ppe->start_method();
}

=head4 parfile_in

Partition file to read, if required.

Set to: existing file.

Default: I<name>.par

=cut

sub _ok_parfile_in {
  my $self=shift;
  my $file=shift;
  CFX5::FileTests::datafile($file);
}
sub _def_parfile_in {
  my $self=shift;
  undef;
}

=head4 parfile_out

Partition file to write, if we write one.

Set to: filename.

Default: I<fullname>.par

=cut

sub _ok_parfile_out {
  my $self=shift;
  my $file=shift;
  CFX5::FileTests::datafile_w($file);
}
sub _def_parfile_out {
  my $self=shift;
  (defined $self->fullname() ? $self->fullname().'.par' : undef);
}
sub _def_parfile_out_ccl {
  my $self=shift;
  $self->parfile_out();
}

=head4 save_parfile

Set to: 1 if we want to save the output from the partitioner, 0 else.

Default: TRUE if the run mode is 'Paritioner Only', FALSE else.

=cut

sub _ok_save_parfile {
  return 0 unless defined $_[1];
  1;
}
sub _def_save_parfile {
  $_[0]->run_mode() eq 'Partitioner Only' ? 1 : '';
}

=head4 part_mode

The mode to use for the partitioner.

Set to: one of
  MeTiS
  Simple Assignment
  Recursive Coordinate Bisection
  Optimized Recursive Coordinate Bisection
  Directional Recursive Coordinate Bisection
  User Specified Direction
  User Partitioning Routine

Default: none (defaults to whatever the partitioner defaults to!)

=cut

sub _ok_part_mode {
  my $self=shift;
  my %okmodes=(
               MeTiS => 1,
               'Simple Assignment' => 1,
               'Recursive Coordinate Bisection' => 1,
               'Optimized Recursive Coordinate Bisection' => 1,
               'Directional Recursive Coordinate Bisection' => 1,
               'User Specified Direction' => 1,
               'User Partitioning Routine' => 1,
              );

  return $okmodes{$_[0]};
}
sub _def_part_mode {
    'MeTiS';
}

=head4 part_size_rule


=cut

sub _ok_part_size_rule {
  my $self=shift;
  my %okmodes=(
               Uniform => 1,
               Automatic => 1,
               Specified => 1,
              );

  return $okmodes{$_[0]};
}
sub _def_part_size_rule {
  'Automatic';
}

=head4 part_weights


=cut

sub _ok_part_weights {
  my $self=shift;
  return defined($_[0]);
}
sub _def_part_weights {
  undef;
}

=head4 part_metis_type

The mode to use for the MeTiS partitioning method.

Set to: one of
  k-way
  recursive bisection

Default: none (defaults to whatever the partitioner defaults to!)

=cut

sub _ok_part_metis_type {
  my $self=shift;
  my %okmodes=(
               'k-way' => 1,
               'recursive bisection' => 1,
              );

  return $okmodes{$_[0]};
}
sub _def_part_metis_type {
  'k-way';
}

=head4 ppe_mode

The mode to use for the parallel run.

Set to: 'pvm' or 'mpi'.

Default: Taken from the configuration variable CFX5_PARALLEL_MODE.

=cut

sub ppe_mode {
  my $self=shift;

  my $ppe=$self->ppe_obj();
  if (@_) {
    $ppe->set_compat_mode(@_);
  } else {
    internal_error("Can't read the PPE mode any more");
  }
}

=head4 ppe_obj

This flag is used to store a copy of the PPE object for this run (see
L<CFX5::Parallel>).  It is created the first time it is accessed.

Set to: An object of type C<CFX5::Parallel> (or derivatives)

Default: created on first call.

=cut

sub _ok_ppe_obj {
  return 0 unless (!defined $_[1] or $_[1]->isa('CFX5::Parallel'));
  1;
}
sub _def_ppe_obj {
  my $self=shift;
  my $ppe=CFX5::Parallel->new($self, '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/PARALLEL ENVIRONMENT');
  # store it for Ron
  $self->{settings}{ppe_obj}=$ppe;
  $ppe;
}

=head4 ppe_running

This flag is mostly used as a cache for internal state, but it may
also be set externally.

Set to: 1 if the PPE is running, 0 if it is not.

Default: 0, but the setting may be changed by the module at any time.

=cut

sub _ok_ppe_running {
  return 0 unless defined $_[1];
  1;
}
sub _def_ppe_running {
  0;
}

=head4 autostop_ppe

Set to: 1 to halt the PPE at the end of the run, if it is running, or
0 not to.

Default: 1.

=cut

sub _ok_autostop_ppe {
  return 0 unless defined $_[1];
  1;
}
sub _def_autostop_ppe {
  1;
}

=head4 host_list_is_set_up

This flag is used as a cache for internal state.

Set to: 1 if the host list has been fully set up, 0 else.

Default: 0, but the setting may be changed by the module at any time.

=cut

sub _ok_host_list_is_set_up {
  return 0 unless defined $_[1];
  1;
}
sub _def_host_list_is_set_up {
  0;
}

=head3 PARALLEL HOSTS AND PARTITIONS - STANDARD STYLE

We use L<CFX5::Parallel> to manage the host lists, see the comments in
there for more details.

This provides us with two modes of specifying the host list.  The
"old" style, which is retained for compatibility with the old solver
manager, uses a host list C<hstlist> and a count C<partitions> of
partitions.  The new style uses the host list to get the count (in
distributed parallel mode), or uses an explicit count in local
parallel mode.

=cut

=head4 host_list

If this setting is given, it specifies a list of hosts to use for the
B<distributed> parallel run, overriding the list given in the
C<hstfile>.  The script will construct a new C<hst> file for the run,
using data from the C<hstfile> (if any, this overrides any data in the
CFXhosts file), and the C<CFXhosts> file.

Set to: a string with a space- or comma-separated list of hosts to run
on, or  undef to disable this feature.

Default: undef.

=cut

# most of the calls in this section access the CCL via the PPE object,
# rather than directly.
sub host_list {
  my $self=shift;
  my $ppe=$self->ppe_obj();
  $ppe->host_list_text(@_);
}

=head4 processes

Sets the number of processes to use in a B<local> parallel run.

Set to: integer > 1

Default: undef

=cut

#'/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/PARALLEL ENVIRONMENT/Number of Processes'
sub processes {
  my $self=shift;
  my $ppe=$self->ppe_obj();
  $ppe->local_processes(@_);
}

=head4 master_host

Sets the host to use as the master for the run, if not the local host.

Set to: host name

Default: undefined

=cut

sub master_host {
  my $self=shift;
  my $ppe=$self->ppe_obj();
  $ppe->master_host(@_);
}

=head3 PARALLEL HOSTS AND PARTITIONS - OLD STYLE

=head4 hstfile

"hst" host definition file, if required.

Set to: existing file.

Default: none.

=cut

sub _ok_hstfile {
  my $self=shift;
  my $file=shift;
  CFX5::FileTests::datafile($file);
}
sub _def_hstfile {
  undef;
}

=head4 hstlist

If this setting is given, it specifies a list of hosts to use for the
parallel run, overriding the list given in the C<hstfile>.  The script
will construct a new C<hst> file for the run, using data from the
C<hstfile> (if any, this overrides any data in the CFXhosts file), and
the C<CFXhosts> file.

Set to: a string with a space- or comma-separated list of hosts to run
on, or  undef to disable this feature.

Default: undef.

=cut

sub hstlist {
  my $self=shift;
  my $ppe=$self->ppe_obj();
  $ppe->host_list_text_old(@_);
}

=head4 partitions

Number of processes to run, when running in Local Parallel mode.  When
running in Distributed Parallel mode, this information will be
generated dynamically from the host list.  In Partitioning Run Only
mode, this information will be used to get the number of partitions if
the host list is not specified. (?)

Default: none

=cut

sub partitions {
  my $self=shift;
  my $ppe=$self->ppe_obj();
  if (@_) {
    # if we're setting it, use the compatibility routines
    _debug("Setting partitions=", $_[0]);
    return $ppe->host_count_old(@_);
  } else {
    # if we're getting it, use the standard access routines
    my $parts=scalar($ppe->partitions());
    _debug("Read partition count from PPE =",
	   (defined $parts ? $parts : '(undef)'));
    if (!$parts and defined $self->parfile_in()) {
      # we don't yet have a partition count, but we do have an input
      # partition file, so use that to set up one.
      my $par=CFX5::Files::Partition->new($self->parfile_in());
      if (defined $par) {
        $parts=$par->num_parts();
        if (defined $parts) {
          $self->partitions($parts);
	  _debug("Read partition count $parts from parfile");
        }
      }
    }
    return $parts;
  }
}


=head3 ADAPTION

The adaption, conceptually, is a sub-process of the solver, and so it
is controlled by the C<do_solve> parameter.  The information on how
many steps, and indeed whether to do adaption at all, is controlled
I<exclusively> by the fields in the definition file.  This is
important, as these are read by the solver as well as the refiner, and
so the script cannot override these.

In general, to modify these settings from a script, the programmer
must modify the definition file before passing it into the job
object.

The following table sets out the correspondance of the definition file
settings to the job object settings:

  Def file                               Job object

  G/ADAPT/ADAPTING                       none
  G/ADAPT/ADAPTING_STATE                 none
  G/ADAPT/ADAPTING_TO_GEOMETRY           adapt_to_geometry()
  G/ADAPT/SAVE_INTERMEDIATE_FILES        adapt_save_intermediate()

The C<adapt_save_intermediate> setting will override the setting from
the definition file, as that is under the control of the script.  The
C<adapt_to_geometry> setting can be explicitly switched off in the
script, which will prevent a database being passed to the refiner
(and, perhaps more importantly, will prevent the script from checking
for a database), but switching it on will have no effect unless it is
also switched on in the definition file.

=head4 adaptinfo

We need to keep the low-level adaption information hash accessible via
a call rather than as a data member, in order to work with SubTasks.

Set to: hashref to parameters for adaption

Default: undef

=cut

sub _ok_adaptinfo {
  1;
}
sub _def_adaptinfo {
  undef;
}

=head4 adapt_control

Set to: Filename of a control file to pass to the refiner, or undef to
pass no control file.

Default: undef

=cut

sub _ok_adapt_control {
  return 1 unless defined $_[1];
  # if we have a filename, it has to be good.
  CFX5::FileTests::datafile($_[1], "Refiner control file $_[1]");
}
sub _def_adapt_control {
  undef;
}

=head4 adapt_initfile

I<Not currently used>.  Do not set this setting.

This setting allows you to specify a "refiner.init" problem setup file
to be passed to the refiner.  Giving such a file overrides the
refinement information in the definition file, and so if such a file
is given, the checking which can be done by the C<Adapt> component is
limited until the refiner has been initialised.

Set to: The location of a "refiner.init" problem setup file for the
refiner, or undef to supress the use of such a file.

Default: undef

=cut

sub _ok_adapt_initfile {
  return 1 unless defined $_[1];
  # if we have a filename, it has to be good.
  CFX5::FileTests::datafile($_[1], "Refiner problem initialisation file $_[1]");
}
sub _def_adapt_initfile {
  undef;
}

=head4 adapt_to_geometry

Set to: 0 to switch off "adapt to geometry" mode in the refiner.
Setting this to 1 will cause the script to believe that adapt to
geometry is on, but unless it is also switched on in the definition
file this will have no effect on the refiner!  So don't do that ...

Default: Until adaption information has been read from the definition
file, the default is C<undef>.  Once it has been read, the default
will be TRUE if it was switched on in the definition file, and we also
have a database; otherwise, FALSE.

=cut '

sub _ok_adapt_to_geometry {
  return 0 unless defined $_[1];
  return 1 if $_[1] == 0 or $_[1] == 1;
  return 0;
}
sub _def_adapt_to_geometry {
  my $self=shift;
  return undef unless defined $self->adaptinfo();
  return undef unless defined $self->adapt_db();
  return $self->adaptinfo()->{geometry};
}

=head4 adapt_db

Set to: Filename of a Build database to use when adapting to
geometry.

Default: none.

=cut

sub _ok_adapt_db {
  return 1 unless defined $_[1];
  # if we have a filename, it has to be good.
  CFX5::FileTests::datafile($_[1], "CFX-Build database $_[1] for adaption to geometry");
}
sub _def_adapt_db {
  undef;
}

=head4 adapt_save_intermediate

This flag controls the saving of the intermediate files from mesh
adaption.  If unset, it will be read from the definition file, but
callers may set this flag explicitly if this behaviour is to be
overridden.

Set to: 1 if the user has requested saving of intermediate results
files, or 0 otherwise.

Default: If adaption information has been read from the definition
file, that provides the default; otherwise, 0.

=cut

sub _ok_adapt_save_intermediate {
  return 0 unless defined $_[1];
  1;
}
sub _def_adapt_save_intermediate {
  my $self=shift;
  return undef unless defined $self->adaptinfo();
  return $self->adaptinfo()->{save_intermediate};
}

=head4 suppress_adapt

I<For debugging only>

Set to: TRUE to switch off checking for adaption datasets in the
definition file.

Default: FALSE.

=cut

sub _ok_suppress_adapt {
  return 0 unless defined $_[1];
  1;
}
sub _def_suppress_adapt {
  0;
}

=head3 BEAUTIFICATION

=head4 text_command

Sets the text for the line

  Using the command: cfx5solve -def deffile.def

at the top of the output file.

Set to: A string containing the command which was used.  Newlines may
be used to split the lines up.  Set to C<undef> to supress the
message.

This can also be set to an anonymous list reference, in which case
each item is taken to be an individual switch, and they will be
formatted appropriately, by adding quotes, line wrapping, and so on.
Hence you can just do

  $job->text_command(\@ARGV);

Default: C<undef>

=cut

sub _ok_text_command {
  1;
}
sub _def_text_command {
  undef;
}

=head3 LIVE SETTINGS

The settings in this section have dynamically calculated defaults, and
are used to represent quantities which can change throughout a run.
They are expressed as settings, rather than an explicit function call,
so that the user can set them explicitly if desired.

=head4 stop_now

Flag to tell the process to stop.

Set to: FALSE to continue, or TRUE to stop the process and tidy up.
Additionally, the TRUE value should be 1 for a user stop, or >1 for an
error stop.

Default: If an error has been signalled at any point, defaults to
TRUE.  Otherwise, if there is no working directory, defaults to FALSE.
If there is a working directory object defined, looks in that
directory for a file called "stp" or "solver.sta", returns TRUE if 
"stp" or "solver.sta" file exists for non-SC runs. For system
coupling runs, always return FALSE regardless if "stp" or 
"solver.sta" exists or not

=cut

sub _ok_stop_now {
  my $self=shift;
  my $v=shift;
  defined $v;
}
sub _def_stop_now {
  my $self=shift;
  return 2 if error_pending();
  return '' unless $self->have_setting('rundir_obj');
  my $rd=$self->rundir_obj();
  return (($rd->have_file('stp') || $rd->have_file('solver.sta')) && !$self->is_sc_run()) ? 1 : '';
}

=head4 interrupt_now

Flag to tell the process to stop I<without tidying up>.  This is used
by the -prepare option to abort in a clean way, without removing the
working directory and so on.  The stop will happen only between
operations.

Set to: FALSE to continue, or TRUE to stop the process.

Default: FALSE

=cut

sub _ok_interrupt_now {
  my $self=shift;
  my $v=shift;
  defined $v;
}
sub _def_interrupt_now {
  my $self=shift;
  '';
}

=head4 first_solver_run

This flag is initially set to TRUE, and is reset to FALSE after the
first run of the solver.  Its purpose is to control the output of the
problem setup from the solver.

Set to: You should not need to set this explicitly.

Default: TRUE until the first solver run, and FALSE after that.

=cut

sub _ok_first_solver_run {
  my $self=shift;
  my $v=shift;
  defined $v;
}
sub _def_first_solver_run {
  my $self=shift;
  1;
}

=head4 have_warned_solver_arch

This flag is initially set to FALSE, and is reset to TRUE when the
architecture is checked for sensibility.

Set to: You should not need to set this explicitly.

=cut

sub _ok_have_warned_solver_arch {
  my $self=shift;
  my $v=shift;
  defined $v;
}
sub _def_have_warned_solver_arch {
  my $self=shift;
  '';
}

=head3 REGRESSION TESTING, DEBUGGING AND INTERNAL HOOKS

=head4 process_ccl

Flag to indicate what sort of processing of CCL in the definition file
is required.

Set to: one of the following:

=over

=item 'default'

Process CCL if any is found, skip it otherwise.

=item 'always'

Always attempt to process CCL in the definition file.  If none can be
found, signals an error.

=item 'never'

Skip any check for CCL, and presumably use the CFX-5.3 data
structures.

=back

Default: 'default'

=cut

sub _ok_process_ccl {
  my $self=shift;
  my $set=shift;
  my %valid = (
	       default => 1,
	       always => 1,
	       never => 1
	      );
  return $valid{$set} ? 1 : '';
}
sub _def_process_ccl {
  # previous misspelling strikes again ...
  if ($Config{CFX5_SUPRESS_CCL} or $Config{CFX5_SUPPRESS_CCL}) {
    return 'never';
  }
  'default';
}

=head4 deffile_no_ccl

Flag to indicate definition files without CCL, ie, mesh-only mode
files.  This is set by C<read_def_ccl>.

Set to: TRUE if a def file is read and contains no ccl.  FALSE if a
def file is read and contains CCL.

Default: undefined

=cut

sub _ok_deffile_no_ccl {
  my $self=shift;
  my $set=shift;
  defined $set;
}
sub _def_deffile_no_ccl {
  undef;
}

=head4 save_workdir

Set to: TRUE to suppress deletion of the working directory at the end
of the run, FALSE to allow it.

Default: FALSE

=cut

sub _ok_save_workdir {
  return 0 unless defined $_[1];
  1;
}
sub _def_save_workdir {
  0;
}

=head4 lets_pretend

Flag to switch off most external processing, use for regression
testing.  Its major effect is to reduce CPU time taken; it still
requires the presence of executables and so on where necessary, it
just doesn't run them.

Set to: 0 for normal operation, 1 to skip external stuff.

Default: 0

=cut '

sub _ok_lets_pretend {
  return 0 unless defined $_[1];
  1;
}
sub _def_lets_pretend {
  0;
}

=head4 write_debug

Flag for debug handling.  Set to 1 to enable the debug/trace
information, FALSE to disable.

Set to: TRUE or FALSE.

Default: whatever lets_pretend is set to

=cut

sub _def_write_debug { $_[0]->lets_pretend(); }
sub _ok_write_debug  { 1; }

=head4 debug_obj

This setting holds the object used for handling debugging, if there is
one.  It will instantiate the object automatically if called -
including loading the code - so it should only be referenced if
debugging is required.  This should never be done in code which will
be called by users, as it will not be compiled in on Windows machines.

=cut

sub _def_debug_obj {
  my $self=shift;

  eval { require CFX5::Simulation::Configuration::Debug };
  if ($@) {
    _warn("Unable to load debugging code for the ANSYS CFX Solver Job",
    "object: $@"); return undef;
  }

  my $dbg=CFX5::Simulation::Configuration::Debug->new($self);
  # record it for Ron
  $self->{settings}->{debug_obj}=$dbg;
  # and return it
  $dbg;
}
sub _ok_debug_obj {
  1;
}

=head4 checkpoint_workdir

Flag to switch on checkpointing in L<CFX5::WorkDir> (see
L<CFX5::WorkDir/enable_checkpoints>).  This can be used as a debugging
aid to track what happens to files in the working directory during a
run.  Note that this setting is only currently used at the beginning
of a run.

Set to: 1 to enable checkpointing, 0 to disable.

Default: 0

=cut

sub _ok_checkpoint_workdir {
  return 0 unless defined $_[1];
  1;
}
sub _def_checkpoint_workdir {
  0;
}

=head4 interrupt_after

This flag is used to terminate the run prematurely.  It is a more
general replacement for the -prepare switch from CFX-5, necessary
because the solver loop is now much more complex.

The value should be a string from the following list, and the job will
be terminated I<after> the step mentioned:

=over

=item preparedef

The definition file has been created from the initial values and
definition files, and CCL processing has been performed.  This is just
before the solver runs.

=item partitioner

The partitioning step has been run, but the solver has not yet been
started.

=item solver

The solver has just been run.  (Useful for testing the refiner).

=item refiner

The refiner has just been run.

=back

In addition, these strings may be followed by an asterisk and a
number, in which case the step may be performed more than once.  For
example, a setting of 'C<solver*2>' on a problem with mesh adaption
will run the solver, the refiner, and the solver a second time before
exiting.

Set to: a string as above.

Default: undef

=cut

sub _ok_interrupt_after {
  return 0 unless defined $_[1];
  my ($where,$count)=split(/\*/,$_[1]);
  unless (!defined $count or $count =~ m/^[0-9]+$/) {
    _error("Count $count in interrupt_after setting $_[1] is not an integer");
    return 0;
  }
  my @valid=qw(initial preparedef partitioner solver refiner Remeshing);
  unless (grep { $_ eq $where } @valid) {
    _error("Where string $where in interrupt_after setting $_[1] is not valid");
    return 0;
  }
  1;
}
sub _def_interrupt_after {
  undef;
}

=head4 stages_run

This setting records the stages which have already been run in this
run, for use by C<interrupt_after>.  It is not usually set by users.

Set to: A list reference containing a list of stages.

Default: a reference to an empty list.

=cut

sub _ok_stages_run {
  return 0 unless defined $_[1] and ref($_[1]) eq 'ARRAY';
  1;
}
sub _def_stages_run {
  [];
}

#
# MFX Settings
#

# MFX run type
sub _ok_mfx_run_type {
 my %ok=( undef => 1,
	  'None' => 1,
          'ANSYS MultiField' => 1,
	  'ANSYS MultiField via Prep7' );
 return 0 unless $ok{$_[1]};
 1;
}
sub _def_mfx_run_type {
  return undef;
}

sub _ok_is_mfx_run {
 1;
}
sub _def_is_mfx_run {
  my $self=shift;
  defined $self->mfx_run_type() and $self->mfx_run_type() =~ m/ANSYS MultiField/;
}

sub start_ansys {
  my $self=shift;
  return 0 unless $self->is_mfx_run();
  return 0 if $self->run_mode() eq "Partitioner Only";
  if ( $self->mfx_run_mode() eq "Start ANSYS and CFX" or $self->mfx_run_mode() eq "Start ANSYS only" ){
   return 1;
 } else {
   return 0;
 }
}

sub prep_ansys {
  my $self=shift;
  # Preprocess also prep7
  return 0 if $self->run_mode() eq "Partitioner Only";
  return 0 unless defined $self->mfx_run_mode();
  return 0 if $self->mfx_run_mode() eq "Start CFX only";
  return 1 if defined $self->mfx_run_type() and $self->mfx_run_type() eq "ANSYS MultiField via Prep7";
  return 0 unless defined $self->mfx_run_type() and $self->mfx_run_type() eq "ANSYS MultiField";
  return 1 if $self->mfx_run_mode() eq "Process Input File only";
  return 0 unless $self->mfx_run_mode() =~ m/Start ANSYS/g;
  return $self->mfx_process_input() =~ m/On|Yes|1/; 
}
  

sub start_cfx {
  my $self=shift;
  return 0 unless $self->run_mode() eq "Full";
  return $self->prep_cfx();
}

sub prep_cfx {
  my $self=shift;
  return 1 unless defined $self->mfx_run_mode();
  if( $self->mfx_run_mode() eq "Start ANSYS and CFX" or $self->mfx_run_mode() eq "Start CFX only" ){
    return 1;
  } else {
    return 0;
  }
}

# MFX restart
sub _ok_mfx_restart_db {
  1;
}
sub _def_mfx_restart_db {
  undef;
}

sub _ok_mfx_restart {
   my %ok=( 'On' => 1,
            'Yes' => 1, 
	    'Off' => 1,
	    'No' => 1,
    	    '1' => 1,
            '0' => 1);
 return 0 unless $ok{$_[1]};
 return 1;
}
sub _def_mfx_restart {
  return 'Off';
}

sub _ok_is_mfx_restart {
 1;
}
sub _def_is_mfx_restart {
  my $self=shift;
  return 1 if $self->mfx_restart() =~ m/On|Yes|1/;
  return 0;
}

sub _ok_mfx_restart_type {
 my %ok=( 'Singleframe' => 1,
           'Multiframe' => 1,
           'unknown' => 1); 
 return 0 unless $ok{$_[1]};
 return 1;
}
sub _def_mfx_restart_type {
  return 'unknown';
}

sub _ok_is_multiframe_restart {
 1;
}
sub _def_is_multiframe_restart {
  my $self=shift;
  return 1 if $self->mfx_restart_type() eq 'Multiframe';
  return 0;
}


# MFX Run Mode
sub _ok_mfx_run_mode {
  my %ok=( 'Start ANSYS and CFX' => 1,
            'Start ANSYS only' => 1,
            'Start CFX only' => 1,
            'Process Input File only' => 1); # i.e NONE
 return 0 unless $ok{$_[1]};
 return 1;
 1;
}
sub _def_mfx_run_mode {
  my $self=shift;
  if ( $self->is_mfx_run() ) {
    return "Start ANSYS and CFX";
  }
  undef;
}

# Process ANSYS input file
sub _ok_mfx_process_input {
  my %ok=( 'On' => 1,
           'Off' => 1,
   	   'Yes' => 1,
           'No' =>  1,
           '1' => 1,
           '0' => 1);
 return 0 unless $ok{$_[1]};
 return 1;
 1;
}
sub _def_mfx_process_input {
  my $self=shift;
  if( $self->start_ansys() or $self->mfx_run_mode() eq 'Process Input File only' ) {
    return "On";
  } else {
    return "Off";
  }
}

# Ansys installation - default to AWP_ROOT<Version> environment variable
sub _ok_ansys_install_root {
 my $self=shift;
 my $file=shift;
 CFX5::FileTests::datadir($file, "ANSYS Installation Root") if defined $file;
 1;
}
sub _def_ansys_install_root {
  return undef unless defined $Config{ANSYSINSTALLROOT};
  return join_file($Config{ANSYSINSTALLROOT}, "ansys");
}

# Ansys solver executable
sub _ok_ansys_exe {
  my $self=shift;
  my $file=shift;
  CFX5::FileTests::execfile($file, "ANSYS Solver Executable");
}
sub _def_ansys_exe {
  my $self=shift;
  my $ansys_command;
  if($Config{CFX5OS} eq "winnt"){
    $ansys_command=join_file($self->ansys_install_root(), "bin", "intel", "ANSYS" . $Config{CFXBUILDID_NOSEP_MAJOR_MINOR} . ".exe");
  } elsif($Config{CFX5OS} eq "winnt-amd64"){
    $ansys_command=join_file($self->ansys_install_root(), "bin", "winx64", "ANSYS" . $Config{CFXBUILDID_NOSEP_MAJOR_MINOR} . ".exe");
  } else {
    $ansys_command=join_file($self->ansys_install_root(), "bin", "ansys" . $Config{CFXBUILDID_NOSEP_MAJOR_MINOR});
  }
  return $ansys_command;
}

# ICEM installation
sub _ok_icem_install_root {
 my $self=shift;
 my $file=shift;
 CFX5::FileTests::datadir($file, "ICEMCFD Installation Root") if defined $file;
 1;
}
sub _def_icem_install_root {
  return undef unless defined $Config{ICEMINSTALLROOT};
  return join_file($Config{ICEMINSTALLROOT});
}


# ICEM executable
sub _ok_icem_exe {
  my $self=shift;
  my $file=shift;
  CFX5::FileTests::execfile($file, "ICEMCFD Executable");
}
sub _def_icem_exe {
  my $self=shift;
  my $icem_command;
  if($Config{CFX5OS} eq "winnt" or $Config{CFX5OS} eq "winnt-amd64"){
    $icem_command=join_file($self->icem_install_root(), 'bin', "icemcfd.bat");
  } else {
    $icem_command=join_file($self->icem_install_root(), 'bin', "icemcfd");
  }
  return $icem_command;
}
  
# Ansys Working Directory
sub _ok_ansys_workdir {
 1;
}
sub _def_ansys_workdir {
  my $self=shift;
  
  if(defined $self->fullname()) {
    return fullpath($self->fullname().'.ansys');
  }
  undef;
}

# Host that Ansys is running on, defaults to local host
sub _ok_ansys_host {
  return 1;
}
sub _def_ansys_host {
  return $Config{CFX_HOST};
}

# Port for Solver comms, can be set in ccl or determined by Ansys solver
sub _ok_ansys_port {
  my $self=shift;
  my $port=shift;
  # Check that the port number is sane
  if( $port < 0 or $port > 65535 ){
    _error("Solver Coupling Port out of range");
    return 0;
  }
  return 1;
}
sub _def_ansys_port {
  return undef;
}

sub _ok_ansys_port_file {
  my $self=shift;
  my $file=shift;
  # Remove port file if it already exists
  if(CFX5::FileStat::test_e $file){
    unlink $file
  } 
  return 1;
}
sub _def_ansys_port_file {
  my $self=shift;
  return join_file($self->ansys_workdir(),$self->ansys_jobname().".port");
}

# Ansys Input File
# Default to the file in CCL

sub _ok_ansys_input {
  my $self=shift;
  my $file=shift;
  defined $file and CFX5::FileTests::datafile($file, "ANSYS input file");
}
sub _def_ansys_input {
  undef;
}

sub _ok_processed_ansys_input {
  my $self=shift;
  my $file=shift;
  defined $file and CFX5::FileTests::datafile($file, "Processed ANSYS input file");
}
sub _def_processed_ansys_input {
  my $self=shift;
  if ($self->prep_ansys()) {
    return join_file($self->ansys_workdir(), $self->ansys_jobname().".mf");
  } else {
    return $self->ansys_input();
  }
}
	

# Ansys Ouput File -default to jobname.stdout
sub _ok_ansys_output {
  my $self=shift;
  my $file=shift;
  CFX5::FileTests::datafile_w($file, "ANSYS output file");
}
sub _def_ansys_output {
  my $self=shift;
  return join_file($self->ansys_workdir(),$self->ansys_jobname().".stdout");
}

sub _ok_ansys_nlh {
  1;
}
sub _def_ansys_nlh {
  my $self=shift;
  if( $self->start_ansys() ){
    return join_file($self->ansys_workdir(),$self->ansys_jobname().".nlh");
  }
  undef;
}

sub _ok_ansys_gst {
  1;
}
sub _def_ansys_gst {
  my $self=shift;
  if( $self->start_ansys() ){
    return join_file($self->ansys_workdir(),$self->ansys_jobname().".gst");
  }
  undef;
}



# ANSYS jobname - default to "ANSYS"
sub _ok_ansys_jobname {
 my $self=shift;
 my $name=shift;
 # Warn if it already exists
 if( defined $name and $name){
   return 1;
 }
 return 0;
}
sub _def_ansys_jobname {
  my $self=shift;
  return "ANSYS";
}

# Extra ANSYS args
sub _ok_ansys_args {
 1;
}
sub _def_ansys_args {
  return undef;
}

# Ansys license to use
sub _ok_ansys_license {
 1;
}
sub _def_ansys_license {
  return undef;
}

#
# General System Coupling Settings
# 

# CFX Host Port
sub _ok_scport {
  return 1;
}
sub _def_scport {
  return undef;
}

# CFX Host Name
sub _ok_schost {
  return 1;
}
sub _def_schost {
  return undef;
}

# CFX System Name
sub _ok_scname {
  return 1;
}
sub _def_scname {
  return undef;
}

# CFX Licensing Port Host
sub _ok_sclic {
  return 1;
}
sub _def_sclic {
  return undef;
}

# CFX System Coupling ID
sub _ok_scid {
  return 1;
}
sub _def_scid {
  return undef;
}

sub is_sc_run {
  my $self=shift ;
  defined $self->scport() or defined $self->schost() or defined $self->scname() or defined $self->sclic() or defined $self->scid();
}

#
# General MFX Settings
# 

# CFX Solver Process name
sub _ok_cplg_name {
  return 1;
}
sub _def_cplg_name {
  my $self=shift;
  return 'CFX';
}

# Have we started Ansys
sub _ok_ansys_running {
  1;
}
sub _def_ansys_running {
  0;
}

# What is the Ansys solver pid
sub _ok_ansys_pid {
  1;
}
sub _def_ansys_pid {
  undef;
}

#Moved from cfx5solve

sub bak_elapsed_time {
  my $self=shift;
  my $time=shift;
  $self->set_ccl_parm("/FLOW/OUTPUT CONTROL/BACKUP RESULTS:cmdline/Option" => 'Standard');
  $self->set_ccl_parm("/FLOW/OUTPUT CONTROL/BACKUP RESULTS:cmdline/File Compression Level" => 'Default');
  $self->set_ccl_parm("/FLOW/OUTPUT CONTROL/BACKUP RESULTS:cmdline/OUTPUT FREQUENCY/Option" => 'Elapsed Time Interval');
  $self->set_ccl_parm("/FLOW/OUTPUT CONTROL/BACKUP RESULTS:cmdline/OUTPUT FREQUENCY/Elapsed Time Interval" => $time);  
}

sub max_elapsed_time {
  my $self=shift;
  my $time=shift;
  $self->set_ccl_parm("/FLOW/SOLVER CONTROL/ELAPSED WALL CLOCK TIME/Option" => 'Maximum Run Time');
  $self->set_ccl_parm("/FLOW/SOLVER CONTROL/ELAPSED WALL CLOCK TIME/Elapsed Time" => $time);
}

sub _ok_multidomain_partitioning_option {
  1;
}
sub _def_multidomain_partitioning_option {
  undef;
}

sub _ok_preferred_license {
  1;
}
sub _def_preferred_license {
  undef;
}

sub _ok_shared_license_port {
  1;
}
sub _def_shared_license_port {
  undef;
}

sub _ok_size_nr {
  1;
}
sub _def_size_nr {
  undef;
}

sub _ok_size_ni {
  1;
}
sub _def_size_ni {
  undef;
}

sub _ok_size_nd {
  1;
}
sub _def_size_nd {
  undef;
}

sub _ok_size_nc {
  1;
}
sub _def_size_nc {
  undef;
}

sub _ok_size_nl {
  1;
}
sub _def_size_nl {
  undef;
}

sub _ok_size_part_nr {
  1;
}
sub _def_size_part_nr {
  undef;
}

sub _ok_size_part_ni {
  1;
}
sub _def_size_part_ni {
  undef;
}

sub _ok_size_part_nd {
  1;
}
sub _def_size_part_nd {
  undef;
}

sub _ok_size_part_nc {
  1;
}
sub _def_size_part_nc {
  undef;
}

sub _ok_size_part_nl {
  1;
}
sub _def_size_part_nl {
  undef;
}


# Sigh.. once more
sub _ok_size_interp_nr {
  1;
}
sub _def_size_interp_nr {
  undef;
}

sub _ok_size_interp_ni {
  1;
}
sub _def_size_interp_ni {
  undef;
}

sub _ok_size_interp_nd {
  1;
}
sub _def_size_interp_nd {
  undef;
}

sub _ok_size_interp_nc {
  1;
}
sub _def_size_interp_nc {
  undef;
}

sub _ok_size_interp_nl {
  1;
}
sub _def_size_interp_nl {
  undef;
}

# Sigh.. another one
sub _ok_size_cclsetup_nr {
  1;
}
sub _def_size_cclsetup_nr {
  undef;
}

sub _ok_size_cclsetup_ni {
  1;
}
sub _def_size_cclsetup_ni {
  undef;
}

sub _ok_size_cclsetup_nd {
  1;
}
sub _def_size_cclsetup_nd {
  undef;
}

sub _ok_size_cclsetup_nc {
  1;
}
sub _def_size_cclsetup_nc {
  undef;
}

sub _ok_size_cclsetup_nl {
  1;
}
sub _def_size_cclsetup_nl {
  undef;
}

sub _ok_numa {
 1;
}
sub _def_numa {
    'None';
}

sub _ok_affinity {
 1;
}
sub _def_affinity {
    'Implicit';
}
#
#===============================================================================
#  Configuration/Simulation Settings (will likely need reorganisation)
#===============================================================================
#
sub _ok_resInfo {
  1 ; 
}
sub _def_resInfo {
  my $self=shift ;
  
  #my $resInfo = new CFX5::ResInfo() ; 
  
  #$self->{settings}->{resInfo} = $resInfo ;
  $self->{'parent'}{'resinfo'};
}

#
#===============================================================================
#  Remeshing Related Settings
#===============================================================================
#
# Definition file created by Pre during remeshing
#
sub _ok_defRemesh {
  1;
}
sub _def_defRemesh { undef; }
#
sub _ok_resRemesh {
  1;
}
sub _def_resRemesh { undef; }
#
sub _ok_outRemesh {
  1;
}
sub _def_outRemesh { undef; }

#
# Number of remeshing object definitions in the configuration 
#
sub _ok_num_remesh {
  1;
}
sub _def_num_remesh {
  0 ;
}
#
# Array of Remesh objects
#
sub _ok_remeshObjs {
  1;
}
sub _def_remeshObjs {
  my $self=shift;

  #  Connect to CCL 
  my $cclapi=$self->ccl_api_obj() ;
  unless (defined $cclapi) {
    _error "Remeshing: Could not connect to CCL" ;
  }
  
  # Get reference to run directory 
  my $outObj  = $self->outobj();
  my $rundir  = $self->rundir_obj();

  my $nRemesh = 0 ;
  my @remeshObjs ;

  my @configList ;
  $cclapi->getObjectsByType("CONFIGURATION",\@configList) ;
  
  foreach my $config (@configList) {

    my @CCLChildren ;
    $cclapi->getChildObjectList($config,\@CCLChildren) ;
    foreach my $child (@CCLChildren) {

      # Parse object and skip if parse fails or if not REMESH object 
      next unless ($child =~/(.*):(.*)/) ;  
      next unless (defined $1 && defined $2) ; 
      next unless ($1 eq "REMESH") ; 
      my $name = $2; 

      my $remesh_dir = $config."/".$child ;

      # Pickup and store additional information
      my $option ; 
      my $meshReloadOptions ; 
      $cclapi->getStringValue($remesh_dir."/Option",\$option) ;
      $cclapi->getStringValue($remesh_dir."/Mesh Reload Options",\$meshReloadOptions) ;

      # Create new remesh object and increment counter
      push(@remeshObjs, 
        new CFX5::Simulation::Configuration::Remeshing::Remesh(
                "CCLPath"        => $remesh_dir, 
                "CCLAPI"            => \$cclapi, 
                "Option"            => $option,
                "Name"              => $name,
                "RunDir"            => \$rundir,
                "OutObj"            => \$outObj,
                "Parent"            => $self,
                "MeshReloadOptions" => $meshReloadOptions
              )
      );
      $nRemesh++ ;
    }

    # Record number of remesh objects 
    $self->num_remesh($nRemesh) ;
  }

  #  Return a reference to the array
  $self->{settings}->{remeshObjs} = \@remeshObjs ;
}

#
#===============================================================================
#  Run Initialisation Handling
#===============================================================================
#

sub _ok_cont_hist_from {
  1;
}
sub _def_cont_hist_from {
  undef ;
}

sub _ok_use_mesh_from {
  1;
}
sub _def_use_mesh_from {
  undef ;
}

sub _ok_continue_history {
  1;
}

sub _def_continue_history {
  my $self=shift ;
  return 1 if (defined $self->cont_hist_from());
  0; 
}

sub dont_continue_history {
  my $self=shift;
  my $parmPath='/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/INITIAL VALUES SPECIFICATION/INITIAL VALUES CONTROL/Continue History From'; 
  $self->delete_ccl_parm($parmPath) if $self->have_ccl_parm($parmPath) ; 
  1; 
}

sub NumIniValsObj {
  my $self = shift;
  
  my $nObjs=0;
  
  my $cclapi=$self->ccl_api_obj() ;
  unless (defined $cclapi) {
    _error "NumIniValsObj: Could not connect to CCL" ;
  }

  my @iniValObjs ;
  my $rv = $cclapi->getObjectsByType("INITIAL VALUES",\@iniValObjs) ;
  $nObjs = scalar(@iniValObjs) unless (defined $rv) ;

  return $nObjs; 
}

sub DelIniValsObjs {
  my $self = shift ;

  my $api=$self->ccl_api_obj() ;
  unless (defined $api) {
    _error "DelIniValsObjs: Could not connect to CCL" ;
  }

  my @IVObjs ;
  $api->getObjectsByType("INITIAL VALUES",\@IVObjs) ;
  foreach my $IVObj (@IVObjs) {
    $self->delete_ccl_obj ($IVObj);
  }
  
  1; 
}

sub AddIniValsObj {
  my $self = shift;
  my $fileName = shift;
  my $objType = shift; 
  my $continueFromThis = shift ;
  my $umfDefault = shift; 

  my $cclapi=$self->ccl_api_obj() ;
  unless (defined $cclapi) {
    _error "AddIniValsObj: Could not connect to CCL" ;
  }
  
  my $rv;
  my $numIniValsObj = $self->NumIniValsObj() + 1 ;
  my $nameIVObj  = "Initial Values $numIniValsObj";

  #
  # Set the Use Mesh From value
  # --> If there is more than one initial values object, then we MUST use
  #     the 'Solver Input File', otherwise
  # --> Come up with and set a default value, provided that it hasn't
  #     already been set
  my $umfVal ; 
  my $umfPath = "/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/"
               ."INITIAL VALUES SPECIFICATION/INITIAL VALUES CONTROL/Use Mesh From" ;
  if ( $numIniValsObj > 1) {
    $umfVal = "Solver Input File" ;
  }
  else {
    if ($cclapi->haveParameter($umfPath)) {
      $cclapi->getStringValue($umfPath,\$umfVal) ;
    }
    else {
      if (defined $Config{CFX5_SOLVE_USE_IV_MESH}){
        my $rv=CFX5::CCLI::Defaulted->interpret_logl($Config{CFX5_SOLVE_USE_IV_MESH});
        if (defined $rv) {
          if ($rv) {
            $umfVal = "Initial Values" ;
          } else {
            $umfVal = "Solver Input File" ;
          }
        } else {
           # Invalid value for CFX5_SOLVE_USE_IV_MESH configuration: no interpolator.
           $umfVal = $umfDefault ;
        }
      }
      else {
        $umfVal = $umfDefault ;
      }
    }
  }

  my %ivInput = ('Results File'          => 'File Name',
                 'Configuration Results' => 'Configuration Name',
                ); 

  my @ccl ;
  push(@ccl,  "SIMULATION CONTROL:");
  push(@ccl,  "  EXECUTION CONTROL:");
  push(@ccl,  "    RUN DEFINITION:");
  push(@ccl,  "      INITIAL VALUES SPECIFICATION:");
  push(@ccl,  "        INITIAL VALUES: $nameIVObj");
  push(@ccl,  "          Option = $objType");
  push(@ccl,  "          $ivInput{$objType} = $fileName");
  push(@ccl,  "        END");
  push(@ccl,  "        INITIAL VALUES CONTROL:");
  push(@ccl,  "          Use Mesh From = $umfVal");
  if ($continueFromThis) {
    push(@ccl,"          Continue History From = $nameIVObj");
  }
  push(@ccl,  "        END");
  push(@ccl,  "      END");
  push(@ccl,  "    END");
  push(@ccl,  "  END");
  push(@ccl,  "END");

  $rv = undef;
  $rv=$cclapi->parseCCLData(\@ccl, '');
  if (defined $rv) {
    _error("Failed to create intial values object for file: $fileName\n",
           $self->format_ccl_error($rv));
    return 0;
  }
  1;
}

sub ReportIniFilesFromConfigs {
  my $self = shift;
  
  my $file ; 
  my @iniFiles ;
  my $RIPath = "/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/INITIAL VALUES SPECIFICATION" ;
  
  my $cclapi=$self->ccl_api_obj() ;
  unless (defined $cclapi) {
    _error "inifile: Could not connect to CCL" ;
  }
  
  my @iniValObjs ;
  my $rv = undef ; 
  $rv = $cclapi->getObjectsByType("INITIAL VALUES",\@iniValObjs) ;
  return (1) if ($rv) ; 
  
  my @summary ; 
  foreach my $ivObj (@iniValObjs) {
    $ivObj =~/(.*):(.*)/ ;  
    my $ivObjName = $2;
    my $option ;
    $cclapi->getStringValue("$ivObj/Option",\$option) ;
    if ($option eq 'Configuration Results') {
      my $configName ; 
      $cclapi->getStringValue("$ivObj/Configuration Name",\$configName) ;
      my $fileName = $self->GetIniFileFromObj($ivObj) ;
      if (defined $fileName) {
        my @fname = wraptext(50,$fileName); 
        push (@summary,   " Initial Values  : $ivObjName") ;
        push (@summary,   "    Config. Name : $configName") ;
        push (@summary,   "    File Name    : $fname[0]") ;
        for (my $ii = 1; $ii<scalar(@fname); $ii++) {
          push (@summary, "                   $fname[$ii]") ;
        }
        push (@summary, "  ") ;
      }
    }
  }
 
  if (@summary) {
    $self->OutFileHeader('Initial Values Files Supplied by Configurations');
    my $fh=$self->outobj()->open();
    unless ($fh) {
      _error("Cannot write to output file", $self->outfile().": $!");
      return 0;
    }
    
    for (my $ii = 0; $ii<scalar(@summary); $ii++) {
      print $fh "$summary[$ii]\n" ;
    }
  
    unless ($self->outobj()->close()) {
      _error("Cannot finish write to output file", $self->outfile().": $!");
      return 0;
    }
  }
  
  1; 
}

sub GetIniFiles {
  my $self = shift;
  my $badArg = shift ;
  
  # This routine no longer used to set initial values; use AddIniValsObj
  if (defined $badArg) {
    _error("inifilelist routine erroneonsly given argument: $badArg\n") ;
    return 0 ; 
  }
  
  my $file ; 
  my @iniFiles ;
  my $RIPath = "/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/INITIAL VALUES SPECIFICATION" ;
  
  my $cclapi=$self->ccl_api_obj() ;
  unless (defined $cclapi) {
    _error "inifile: Could not connect to CCL" ;
  }
  
  # First look for the Continue History From initial values source
  # --> this needs to be first in the list returned
  my $ivObjContFrom ;
  if ($cclapi->haveParameter("$RIPath/INITIAL VALUES CONTROL/Continue History From")) {
    $cclapi->getStringValue("$RIPath/INITIAL VALUES CONTROL/Continue History From",\$ivObjContFrom) ;
    my $file = $self->GetIniFileFromObj("$RIPath/INITIAL VALUES:$ivObjContFrom") ; 
    push (@iniFiles, $file) if defined ($file) ;
  }
  
  my @iniValObjs ;
  my $rv = undef ; 
  $rv = $cclapi->getObjectsByType("INITIAL VALUES",\@iniValObjs) ;
  return () if ($rv) ; 
  
  foreach my $ivObj (@iniValObjs) {
    $ivObj =~/(.*):(.*)/ ;  
    my $ivObjName = $2;
    # Add file to iniFiles provided that this hasn't happenned already
    unless (defined $ivObjContFrom and
            $ivObjName eq $ivObjContFrom) {
      my $file = $self->GetIniFileFromObj($ivObj) ; 
      push (@iniFiles, $file) if defined ($file) ;
    }
  }  
  
  return @iniFiles ; 
}

#
# Interface routine to return the file name corresponding to a given initial values source
# --> This routine looks after reporting the final results file for configuration based initial values
#
sub GetIniFileFromObj {
  my $self = shift ;
  my $ivObj = shift;

  my $cclapi=$self->ccl_api_obj() ;
  unless (defined $cclapi) {
    _error "GetIniFileFromObj: Could not connect to CCL" ;
  }
  
  my $fileName; 
  
  my $option ;
  $cclapi->getStringValue("$ivObj/Option",\$option) ;
  if    ($option eq 'Results File') {
    $cclapi->getStringValue("$ivObj/File Name",\$fileName ) ;
  }
  elsif ($option eq 'Configuration Results') {
  
    my $sim = $self->{parent}; 
  
    # Get last file written for this config from G/RES_INFO dataset
    # (return undef if no file has been written yet; caller should ignore)
    my $confName ;
    $cclapi->getStringValue("$ivObj/Configuration Name",\$confName ) ;
    my %confResInfo = $sim->{resinfo}->GetLast($confName);
    return undef unless (defined $confResInfo{'FilePath'}) ;
    my $baseFileName = $confResInfo{'FilePath'} ;
    
    # Sort out path to the file
    # --> check working directory, then
    # --> point to directory containing simultion solver input file (.mdef/.mres)
    #     (let regular input file testing machinery fail if this file is missing)
    $fileName = join_file($sim->rundir(),basename($baseFileName));
    unless (-e $fileName) {
      $fileName = fullpath(join_file(dirname($sim->mdeffile()),$baseFileName));
    }
  }
  else {
    _error "GetIniFileFromObj: Unrecognised INITIAL VALUES/Option=$option." ;
    return 0 ; 
  }
  
  return $fileName; 
}

#
# Re implementation of _newsetting based interpolate_iv_int
# --> Needed because we now deal with strings rather than a logical
#
sub interpolate_iv_int {
  my $self = shift;
  my $iVal = shift ; 
  
  my $cclapi=$self->ccl_api_obj() ;
  my $umfVal ;
  my $rv; 
  
  # Merge new setting, if provided
  if (defined $iVal) {
    if ($iVal=~m!yes|on|1!i) {
      $umfVal = "Solver Input File" ;
    }
    else {
      $umfVal = "Initial Values" ;
    }
    
    $self->use_mesh_from($umfVal); 
  }
  else {
    $umfVal = $self->use_mesh_from(); 
  }

  # CCL is missing
  # --> Prior to R12, the default was 'no' interplation
  # --> In R12, we activated interpolation by default; the user needs to turn it off
  if    (!defined $umfVal) {
    return 'yes'; 
  }
  # No interpolation if using mesh from results
  elsif ($umfVal eq "Initial Values") {
    if ($self->NumIniValsObj()>1) {
      _abort ("'Use Mesh From = Initial Values' is only valid with one INITIAL VALUES definition.") ;
    }
    return 'no' ; 
  }
  # Interpolation if using mesh from solver input file
  elsif ($umfVal eq "Solver Input File") {
    return 'yes' ;
  }
  # Error if getStringValue didn't return an error and we haven't recognised
  # the use mesh from value returned
  else {
    _abort ("Unrecognised option, Use Mesh From = $umfVal") ;
  }
}

=head4 use_iv_mesh

Set to: TRUE to use the mesh from the initial values file, or FALSE to
use the mesh from the definition file.

Default: TRUE

TODO: the current default matches the existing 5.6 behaviour.  Should
be changed to do this by default if possible.

=cut

sub use_iv_mesh {
  my $self=shift;

  if (@_) {
    my $to=shift;
    # interpolate_iv_int takes the opposite sense to use_iv_mesh ... :-/
    if ($to) {
      $self->interpolate_iv_int('no');
    } else {
      $self->interpolate_iv_int('yes');
    }
  }

  # get it.  We read the actual CCL, and interpret it as a logical
  my $val=$self->interpolate_iv_int();
  return undef unless defined $val;
  cfx5_require('CFX5::CCLI::Defaulted');
  my $rv=CFX5::CCLI::Defaulted->interpret_logl($val);
  unless (defined $rv) {
    _error("Error in setting \"$val\" for Interpolate Initial Values: should be a logical value (eg 'yes' or 'no').");
  }
  return not $rv;
}



1;
