text:
#include <cmath>
#include <iostream>
#include <iomanip>

#include "location.h"
#include "line.h"
#include "ray.h"
//#include "geometry.h"
#include "ignition_path.h"
#include "flame.h"
#include "flame_series.h"
#include "ffm_settings.h"
#include "ffm_util.h"
#include "pre_heating_flame.h"
#include "stratum_results.h"
#include "forest_ignition_run.h"

bool DUMP_FLAME_LENGTHS_TO_CONSOLE = false;

void dumpFlameLengths(std::map<std::string, std::vector<double>> flameLengths, std::string header) {
  using namespace std;

  if (DUMP_FLAME_LENGTHS_TO_CONSOLE) {
    cout << header << endl;
    cout.setf(ios::fixed|ios::showpoint);
    cout << setprecision(2);

    for ( map<string, vector<double>>::iterator it = flameLengths.begin();
        it != flameLengths.end(); ++it) {

      string name = it->first;
      vector<double>& flens = it->second;

      cout << name << ":";

      for ( vector<double>::iterator vit = flens.begin(); vit != flens.end(); ++vit)
        if (*vit > 0) cout << *vit << ' ';

      cout << endl;
    }

    cout.unsetf(ios::fixed|ios::showpoint);
    cout << setprecision(6);
    cout << endl;
  }
}

/*!\brief Main computation
  \return A Results object after performing the forest fire computations.
*/
Results Location::results() const {

  Results overallResults;

  //do the ignition computation

  //with canopy
  ForestIgnitionRun fir1 = forestIgnitionRun(true);
  overallResults.addRun(fir1);

  ForestIgnitionRun fir2;
  bool runTwoExists = false;
  //if there is spread in the canopy do it with windfield computed using no canopy
  if (fir1.spreadsInStratum(Stratum::CANOPY)){
    fir2 = forestIgnitionRun(false);
    overallResults.addRun(fir2);
    runTwoExists = true;
  }
  //fir is a reference to the second run if it was done, else the first run
  const ForestIgnitionRun& fir = runTwoExists ? fir2 : fir1;

  //****************** surface results **************************************

  double sws = forest_.windProfile(incidentWindSpeed_,
                                   forest_.heightForSurfaceWind(),
                                   !runTwoExists);
  overallResults.surfaceROS(forest_.surface().ros(sws));
  double sfl = forest_.surface().flameLength(sws);
  overallResults.surfaceFlameLength(sfl);
  double sfa = flameAngle(sfl, sws, slope(), firelineLength_);
  overallResults.surfaceFlameAngle(sfa);
  overallResults.surfaceFlameHeight(sfl*sin(sfa) - sfl*cos(sfa)*tan(slope()));

  //****************** stratum results **************************************

  for (const Stratum& strat : forest_.strata()) {

    StratumResults stratResults(strat.level());

    //*************** stratum flame lengths *********************************

    //we'll find representative origins at the same time, these are used to
    //compute flame heights

    double flameLength;
    std::map<std::string, std::vector<double>> selectedFlameLengths;

    // origin of longest flame to use as representative origin for stratum
    // flame height calculations
    Pt longestFlameOrigin;

    // max Y ordinate of an ignited segment end-point to report for strata
    // (allows detecting cases where flame tip height from ground level is
    // less than flame origin height).
    double maxOriginHeight;

    if (strat.level() != Stratum::CANOPY) {
      std::map<std::string, std::vector<double>> plantFlameLengths =
        fir.laterallyMergedSpeciesWeightedPlantFlameLengths(strat.level(), firelineLength_);

      std::map<std::string, std::vector<double>> stratumFlameLengths =
        fir.speciesWeightedFlameLengths(strat.level(), IgnitionPath::STRATUM_PATH);

      double plantFlameLength = ffm_util::cappedMax( plantFlameLengths.at( ForestIgnitionRun::KEY_WEIGHTED_AVERAGE ) );
      double stratumFlameLength = 0.0;

      if (ffm_numerics::gtZero(plantFlameLength)) {
        stratumFlameLength = ffm_util::cappedMax( stratumFlameLengths.at( ForestIgnitionRun::KEY_WEIGHTED_AVERAGE ) );

        if (plantFlameLength > stratumFlameLength){
          flameLength = plantFlameLength;
          longestFlameOrigin = fir.speciesWeightedOriginOfMaxFlame(strat.level(), IgnitionPath::PLANT_PATH);
          selectedFlameLengths = plantFlameLengths;
          maxOriginHeight = fir.highestFlameOriginY(strat.level(), IgnitionPath::PLANT_PATH);

        } else {
          flameLength = stratumFlameLength;
          longestFlameOrigin = fir.speciesWeightedOriginOfMaxFlame(strat.level(), IgnitionPath::STRATUM_PATH);
          selectedFlameLengths = stratumFlameLengths;
          maxOriginHeight = fir.highestFlameOriginY(strat.level(), IgnitionPath::STRATUM_PATH);
        }
      } else { // no plant flames
        flameLength = 0;
        longestFlameOrigin = Pt(0, 0);
        selectedFlameLengths = plantFlameLengths;
        maxOriginHeight = 0;
      }

    } else { //the canopy
      std::map<std::string, std::vector<double>> plantFlameLengths =
        fir1.laterallyMergedSpeciesWeightedPlantFlameLengths(strat.level(), firelineLength_);

      std::map<std::string, std::vector<double>> stratumFlameLengths =
        fir1.speciesWeightedFlameLengths(strat.level(), IgnitionPath::STRATUM_PATH);

      double plantFlameLength = ffm_util::cappedMax( plantFlameLengths.at( ForestIgnitionRun::KEY_WEIGHTED_AVERAGE ) );
      double stratumFlameLength = 0.0;

      if (ffm_numerics::gtZero(plantFlameLength)) {
        stratumFlameLength = ffm_util::cappedMax( stratumFlameLengths.at( ForestIgnitionRun::KEY_WEIGHTED_AVERAGE ) );

        if (plantFlameLength > stratumFlameLength){
          flameLength = plantFlameLength;
          longestFlameOrigin = fir1.speciesWeightedOriginOfMaxFlame(strat.level(), IgnitionPath::PLANT_PATH);
          selectedFlameLengths = plantFlameLengths;
          maxOriginHeight = fir1.highestFlameOriginY(strat.level(), IgnitionPath::PLANT_PATH);

        } else {
          flameLength = stratumFlameLength;
          longestFlameOrigin = fir1.speciesWeightedOriginOfMaxFlame(strat.level(), IgnitionPath::STRATUM_PATH);
          selectedFlameLengths = stratumFlameLengths;
          maxOriginHeight = fir1.highestFlameOriginY(strat.level(), IgnitionPath::STRATUM_PATH);
        }
      } else {
        flameLength = 0.0;
        longestFlameOrigin = Pt(0, 0);
        selectedFlameLengths = plantFlameLengths;
        maxOriginHeight = 0;
      }

      if (runTwoExists) {
        plantFlameLengths =
          fir2.laterallyMergedSpeciesWeightedPlantFlameLengths(strat.level(), firelineLength_);

        stratumFlameLengths =
          fir2.speciesWeightedFlameLengths(strat.level(), IgnitionPath::STRATUM_PATH);

        plantFlameLength = ffm_util::cappedMax( plantFlameLengths.at( ForestIgnitionRun::KEY_WEIGHTED_AVERAGE ) );

        if (ffm_numerics::gtZero(plantFlameLength)) {
          stratumFlameLength = ffm_util::cappedMax( stratumFlameLengths.at( ForestIgnitionRun::KEY_WEIGHTED_AVERAGE ) );

          if(stratumFlameLength > flameLength) {
            flameLength = stratumFlameLength;
            longestFlameOrigin = fir2.speciesWeightedOriginOfMaxFlame(strat.level(), IgnitionPath::STRATUM_PATH);
            selectedFlameLengths = stratumFlameLengths;
            maxOriginHeight = fir2.highestFlameOriginY(strat.level(), IgnitionPath::STRATUM_PATH);
          }

          if (plantFlameLength > flameLength){
            flameLength = plantFlameLength;
            longestFlameOrigin = fir2.speciesWeightedOriginOfMaxFlame(strat.level(), IgnitionPath::PLANT_PATH);
            selectedFlameLengths = plantFlameLengths;
            maxOriginHeight = fir2.highestFlameOriginY(strat.level(), IgnitionPath::PLANT_PATH);
          }
        }
      }
    }
    stratResults.flameLength(flameLength);

    //************* stratum flame angles ***********************

    //use the second run if second run is done
    double flameAng;
    double stratumWindSpeed = forest_.windProfile(incidentWindSpeed_, strat.avMidHt(), !runTwoExists);

    if(strat.level() != Stratum::CANOPY)
      //use slope and wind affected flame angles
      flameAng = flameAngle(flameLength, stratumWindSpeed, slope(), firelineLength_);
    else
      //canopy - use wind affected flame angles
      flameAng = windEffectFlameAngle(flameLength, stratumWindSpeed, slope());

    stratResults.flameAngle(flameAng);

    //************ stratum flame height ************************

    //here we calculate the flame height using the reported flame length and flame angles
    //and representative flame origins computed above.
    //note this as as per specs but the maximum flame height might not be
    //achieved by the flame with the maximum flame length, so maybe needs
    //to be changed later

    double ht = longestFlameOrigin.y() + flameLength * sin(flameAng) -
      (longestFlameOrigin.x() + flameLength * cos(flameAng))*tan(slope());

    stratResults.flameTipHeight(ht);
    stratResults.flameOriginHeight(maxOriginHeight);

    // Representative flame heights for contributing species flames
    for (const Species& spec : strat.allSpecies()) {
      std::map<std::string, std::vector<double>>::iterator it = selectedFlameLengths.find(spec.name());
      if (it != selectedFlameLengths.end()) {  // ie. species has an entry
        double maxLen = ffm_util::cappedMax( it->second );

        ht = longestFlameOrigin.y() + maxLen * sin(flameAng) -
          (longestFlameOrigin.x() + maxLen * cos(flameAng)) * tan(slope());
      }
      else {  // no entry for species - must not even have pre-ignition data
        ht = 0.0;
      }

      stratResults.addSpeciesFlameTipHeight(spec.name(), ht);
    }

    //*********** stratum rate of spread ************************

    double stratROS = 0;

    if (strat.level() == Stratum::NEAR_SURFACE) {
      for (const Species& spec : strat.allSpecies()) {
        double specROS = 0;
        //find the plant path if it exists
        auto i = find_if(fir.beginPaths(), fir.endPaths(),
                         [spec,strat](IgnitionPath ip){return ip.species().sameSpecies(spec) &&
                                                       ip.level() == strat.level() &&
                                                       ip.type() == IgnitionPath::PLANT_PATH;});
        if (i < fir.endPaths()) {
          //weighted sum of plant and surface ros
          double tmp = std::min(1.0,(*i).species().width()/strat.modelPlantSep());
          specROS = tmp * (*i).basicROS() + (1 - tmp)*overallResults.surfaceROS();
        }
        if (fir.spreadsInStratum(strat.level())) {
          //find the stratum path if it exists
          i = find_if(fir.beginPaths(), fir.endPaths(),
                      [strat,spec](IgnitionPath ip){return ip.species().sameSpecies(spec) &&
                                                    ip.level() == strat.level() &&
                                                    ip.type() == IgnitionPath::STRATUM_PATH;});
          if (i < fir.endPaths())
            //species ROS is the max of those of the plant and stratum
            specROS = std::max(specROS, (*i).basicROS());
        }

        //species-weighted sum
        stratROS += specROS * spec.composition();
      } //end loop over strat.allSpecies()
    }

    if (strat.level() == Stratum::ELEVATED || strat.level() == Stratum::MID_STOREY) {
      //if no spread in stratum then ros = 0
      if(!fir.spreadsInStratum(strat.level()))
        stratROS = 0;
      else {
        //is the stratum fire independent? If so, use basic ROS. NOTE that the criterion
        //for independence uses the reported flame angle for strat, which explains why
        //the following code is not contained in ForestIgnitionRun.
        bool independent = false;
        if (stratResults.flameAngle() <= slope() + ffm_settings::independentSpreadSensitivity) {
          //check species-weighted sum of average ros in last two time steps of stratum fire
          double specWeightedSum = 0;
          for (const IgnitionPath& ip : fir.paths()){
            if (ip.type() != IgnitionPath::STRATUM_PATH || ip.level() != strat.level())
              continue;
            if (ip.fullSize())
              specWeightedSum += 0.5*(ip.ros(ip.numSegments() - 1) + ip.ros(ip.numSegments() - 2))*ip.species().composition();
          }
          if (specWeightedSum >= ffm_settings::minRateForStratumSpread)
            independent = true;
        }
        if (independent)
          stratROS = fir.speciesWeightedBasicROS(strat.level());
        else {
          //computed species weighted max distance travelled in stratum, and species
          //weighted time
          double distance = 0, time = 0;
          for (const IgnitionPath& ip : fir.paths()) {
            if (ip.type() != IgnitionPath::STRATUM_PATH || ip.level() != strat.level())
              continue;
            if (ip.spreads()){
              distance += ip.maxHorizontalRun()*ip.species().composition();
              time += (ip.startTimeStep() + ip.numSegments())*ffm_settings::computationTimeInterval*
                ip.species().composition();
            }
          }
          stratROS = time > 0 ? distance/time : 0;
        }
      }
    }

    if (strat.level() == Stratum::CANOPY) {

      if (!fir1.spreadsInStratum(Stratum::CANOPY))
        stratROS = 0;
      else
        stratROS = std::min(fir1.speciesWeightedBasicROS(Stratum::CANOPY),
                            fir1.activeCrownFireROS(overallResults));

      if (runTwoExists){
        double stratROS2;
        if (!fir2.spreadsInStratum(Stratum::CANOPY))
          stratROS2 = 0;
        else
          stratROS2 = std::min(fir2.speciesWeightedBasicROS(Stratum::CANOPY),
                               fir2.activeCrownFireROS(overallResults));
        stratROS = std::max(stratROS, stratROS2);
      }
    }

    stratResults.ros(stratROS);

    //********************* Stratum proportion burnt *************************************

    double maxHeight = fir1.speciesWeightedMaxHeightBurnt(strat.level());
    if (runTwoExists)
      maxHeight = std::max(maxHeight, fir2.speciesWeightedMaxHeightBurnt(strat.level()));
    double propBurnt = (maxHeight - strat.avBottom()) / (strat.avTop() - strat.avBottom());
    propBurnt = std::min(1.0,std::max(0.0,propBurnt));
    stratResults.proportionBurnt(propBurnt);

    overallResults.addStratumResults(stratResults);

  } //end of loop over strata

  //the rest of the overall results

  //********************** Overall rate of spread **************************************

  //max of the ros of each strata and the surface
  if (overallResults.strataResults().empty())
     overallResults.ros(overallResults.surfaceROS());
  else {
    auto i = max_element(overallResults.strataResultsBegin(), overallResults.strataResultsEnd(),
                         [](StratumResults sr1, StratumResults sr2){return sr1.ros() < sr2.ros();});
    overallResults.ros(std::max((*i).ros(), overallResults.surfaceROS()));
  }

  //****************** Wind reduction factor *******************************************

  //computed with canopy on

  if (incidentWindSpeed_ <= 0)
    overallResults.windReductionFactor(1);
  else
    overallResults.windReductionFactor(incidentWindSpeed_ /
                                       forest_.windProfile(incidentWindSpeed_, 1.5, true));

  //*********************** Overall flame angle ****************************************

  //flame length weighted sum of all the strata flame angles
  double flameAngleSum = overallResults.surfaceFlameAngle()*overallResults.surfaceFlameLength();
  double flameLengthSum = overallResults.surfaceFlameLength();
  for (const auto& sr : overallResults.strataResults()) {
    flameAngleSum += sr.flameLength()*sr.flameAngle();
    flameLengthSum += sr.flameLength();
  }
  overallResults.flameAngle(flameLengthSum == 0 ? 0 : flameAngleSum / flameLengthSum);

  //********************** Overall flame length ****************************************

  double overallFlameLength = 0;
  //first find mean flame length of combined flames
  if(!fir.combinedFlames().empty()) {
    std::vector<double> flameLengths;
    flameLengths.resize(fir.combinedFlames().size());
    transform(fir.beginCombinedFlames(), fir.endCombinedFlames(), flameLengths.begin(),
              [](Flame f){return f.flameLength() < 0.01 ? 0 : f.flameLength();});
    overallFlameLength = ffm_util::mean(flameLengths,true);
  }
  //overall flame length should be at least as large as the stratum flame lengths
  for (const auto& sr : overallResults.strataResults())
    overallFlameLength = std::max(overallFlameLength, sr.flameLength());

  overallResults.flameLength(overallFlameLength);

  //********************* Overall flame height *****************************************

  //max of the flame heights of each strata and the surface
  double maxTip = overallResults.surfaceFlameHeight();
  double maxOrigin = 0.0;

  for (std::vector<StratumResults>::iterator it = overallResults.strataResults().begin();
      it != overallResults.strataResults().end(); ++it) {

    maxTip = std::max( maxTip, (*it).flameTipHeight() );
    maxOrigin = std::max( maxOrigin, (*it).flameOriginHeight() );
  }
  overallResults.flameTipHeight( maxTip );
  overallResults.flameOriginHeight( maxOrigin );

  //********************** Scorch heights **********************************************

  //McArthur and Luke - McArthur
  overallResults.scorchHeightMcarthur(5.232*pow(overallResults.flameTipHeight(), 0.7));
  overallResults.scorchHeightLukeMcarthur(6*overallResults.flameTipHeight());

  //byram intensity
  //R.S. McAlpine - Testing the Effect of Fuel Consumption on Fire Spread Rate
  //Int. J. Wildland Fire 5(3): 143-152,1995
  double byramIntensity = 18000*forest_.surface().fuelLoad()*overallResults.ros();

  //Van Wagner scorch heights - from
  //Martin E. Alexander, and Miguel G. Cruz
  //Interdependencies between flame length and fireline intensity in predicting crown fire initiation
  //and crown scorch height
  //International Journal of Wildland Fire 2012, 21, 95-113
  overallResults.scorchHeightVanWagner(0.1483*pow(byramIntensity,0.667));
  //for wind use second run (no canopy) if it exists, else use first run
  //note conversion to km/h
  double wind =  forest_.windProfile(incidentWindSpeed_, 1.2, !runTwoExists)*3.6;
  overallResults.scorchHeightVanWagnerWithWind(0.74183*pow(byramIntensity,0.667)/
                                               (pow(0.025574*byramIntensity + 0.021433*pow(wind,3),0.5)*
                                                (60 - weather_.airTempC())));

  //********************** Type of crown fire *****************************************

  if (overallResults.strataResults().empty())
    overallResults.crownFireType(Results::UNCLASSIFIED);
  else {
    auto i = find_if(overallResults.strataResultsBegin(), overallResults.strataResultsEnd(),
                     [](StratumResults sr){return sr.level() == Stratum::CANOPY;});
    if (i == overallResults.strataResultsEnd() || (*i).flameLength() < 0.5)
      overallResults.crownFireType(Results::UNCLASSIFIED);
    else
      //using second run if it exists (fir)
      overallResults.crownFireType(fir.spreadsInStratum(Stratum::CANOPY) ? Results::ACTIVE : Results::PASSIVE);
  }

  //********************* Crown run length ********************************************

  if (!fir.spreadsInStratum(Stratum::CANOPY))
    overallResults.crownRunLength(0.0);
  else{
    //want the maximum horizontal run in any canopy stratum path for any species
    double maxRun = 0;
    for (const IgnitionPath& ip : fir.paths()) {
      if (ip.level() != Stratum::CANOPY || ip.type() != IgnitionPath::STRATUM_PATH)
        continue;
      maxRun = std::max(maxRun, ip.maxHorizontalRun());
    }
    overallResults.crownRunLength(maxRun);
  }

  //******************** Velocity of crown runs ***************************************

  overallResults.crownRunVelocity(fir.speciesWeightedBasicROS(Stratum::CANOPY));

  //******************** Flame depth **************************************************

  //first do surface
  double maxDepth = forest_.surface().flameResidenceTime() * overallResults.surfaceROS();
  //now iterate over all strata results
  for(const StratumResults& sr : overallResults.strataResults()) {
    if(!fir.spreadsInStratum(sr.level())) continue;
    maxDepth = std::max(maxDepth,
                        forest_.stratum(sr.level()).avFlameDuration() * sr.ros());
  }
  overallResults.flameDepth(maxDepth);

  return overallResults;

}

/*!\brief Provides a complete description of the ignition of the Forest
\param includeCanopy If false then the canopy layer is left out when
computing the wind field.
\return A ForestIgnitionRun object which provides a complete description
of the ignition of the forest, including all plant and stratum
ignition paths and the time series (vector) of combined flames
resulting from the ignition of all strata.

The computation proceeds from the surface up through the strata.
To begin with, the incident flame series and the pre-heating flames
are initialised using the surface fire characteristics. The
computation then proceeds in a loop through the strata, beginning
at the lowest stratum. As each stratum computation has been completed
the results are used to update the incident flame series and the
pre-heating flames for use in the calculations for the next stratum.
For each stratum there are two parts to the computation. Initially
the computation focuses on plant ignition for individual species,
which is handled by a loop over the species in that stratum.
For each species, ignition paths through the plant crown are computed
for each of five ignition scenarios whose initial points are evenly
spaced along the bottom edge of the crown. These ignition paths are
computed by calling the computeIgnitionPath() method, and use the
combined flame series from the surface and lower strata. The
ignition scenario that produces the greatest maximum flame length is
chosen as representative for that species, and the corresponding
plant ignition path is added to the ForestIgnitionRun object being returned.
The series of flames produced by this most successful scenario is
then reordered in decreasing order of flame length. This procedure
is carried out for each of the species in the stratum, resulting in an
ignition path and corresponding re-ordered series of flames for each
species. These series of flames are weighted according to the
composition of the species, and combined into a single species-weighted
plant flame series.

The second part of the computation involves a second loop over the
species in the stratum and uses the species weighted plant flame series
from the first loop as the incident flames to compute stratum ignition
paths for each species. Again, these are computed using the
computeIgnitionPath() method. For each species the stratum ignition
path is added to the ForestIgnitionRun object being returned, and the
series of flames produced by this ignition path is reordered in
decreasing order of flame length. These flame series from each
species are weighted by species composition and combined to produce a
single species-weighted stratum flame series.
The species-weighted plant flame series and species-weighted stratum
flame series are compared. The series with the greater maximum
flame length is selected as that which will represent the stratum
fire, which will be used to estimate the pre-heating component
due to this stratum, and which will be combined with those from the
lower strata to produce the incident flames for plant ignition scenarios
for the next stratum.

The computation then proceeds to the next highest stratum, and the process is repeated.
*/
ForestIgnitionRun Location::forestIgnitionRun(const bool& includeCanopy) const {

  const bool PLANT_IGNITION_RUN = true;

  //ignition paths for species and strata. This is what will be returned by this method
  ForestIgnitionRun ignitionRun(forest());
  ignitionRun.type(includeCanopy ? ForestIgnitionRun::WITH_CANOPY : ForestIgnitionRun::WITHOUT_CANOPY);

  //initialise surface flame timeseries with surface flame characteristics
  double surfWindSpeed = forest_.windProfile(incidentWindSpeed_, forest_.heightForSurfaceWind(), includeCanopy);
  double surfFlameLength = forest_.surface().flameLength(surfWindSpeed);
  double surfFlameAngle = flameAngle(surfFlameLength, surfWindSpeed, slope(), firelineLength_);
  double sfrt = forest_.surface().flameResidenceTime();
  std::vector<Flame> surfaceFlames(static_cast<int>(round(sfrt/ffm_settings::computationTimeInterval)),
                                   Flame(surfFlameLength,surfFlameAngle,Pt(0,0), 0,ffm_settings::mainFlameDeltaTemp));

  //initialise vector of pre-heating flames
  //Note this is a small vector no bigger than 4, one element for each layer that contributes to
  //the preheating
  std::vector<PreHeatingFlame> preHeatingFlames;
  double cumulativePreHeatingStartTime = 0;
  preHeatingFlames.push_back(PreHeatingFlame(Stratum::SURFACE,
                                             Flame(surfFlameLength,surfFlameAngle,
                                                   Pt(0,0),0,ffm_settings::mainFlameDeltaTemp),
                                             cumulativePreHeatingStartTime,
                                             sfrt));

  //initialise preHeatingEndTime to -1 which means that duration will be limited only by the
  //end times of each individual preHeatingFlame. preHeatingEndTime will be set to the
  //start time of direct heating as computation progresses through the strata
  double preHeatingEndTime = -1;

  //vector of FlameSeries for species weighted stratum flames. These together with the surface
  //flames form the incident flames for the plant ignition sequence calculation
  std::vector<FlameSeries> allSpeciesWeightedStratumFlameSeries;

  //if a level exists in the vector flameConnections then a flame connection with
  //higher strata is guaranteed from this level, else have to look
  //at Forest::StrataOverlaps etc.
  std::vector<Stratum::LevelType> flameConnections;

  //loop over the strata
  for ( const auto& strat : strata() ) {

    //compute incident flames for this strata from surface flames and strata flames from lower strata
    //only include lower strata that have a conection with strat, ie vertical association etc
    std::vector<Flame> incidentFlames(surfaceFlames);

    if (!allSpeciesWeightedStratumFlameSeries.empty()) {

      //first we need to compute a flame-weighted wind for use in the flame combination
      double flameSum = surfaceFlames.at(0).flameLength();
      double flameWeightedWind = surfWindSpeed*flameSum;
      for (const auto& s : strata()) {
        if (s >= strat) break;
        //find the flameseries from stratum s
        auto iter = find_if(allSpeciesWeightedStratumFlameSeries.begin(),
                            allSpeciesWeightedStratumFlameSeries.end(),
                            [s](FlameSeries fs){return fs.level() == s.level();});
        if (iter == allSpeciesWeightedStratumFlameSeries.end())
          continue; //no output for stratum s
        //if there is a connection then add the winds
        if (find(flameConnections.begin(), flameConnections.end(), s.level()) < flameConnections.end() ||
            forest_.verticalAssociation(s.level(),strat.level())) {
          double flen = (*iter).cappedMaxFlameLength();
          double w = forest_.windProfile(incidentWindSpeed_, s.avMidHt(), includeCanopy);
          flameWeightedWind += flen * w;
          flameSum += flen;
        }
      }
      flameWeightedWind = flameSum > 0 ? flameWeightedWind / flameSum : 0;

      //now compute the combined flames
      for (const auto& s : strata()) {
        if (s >= strat) break;
        //find the flameseries for stratum s
        auto iter = find_if(allSpeciesWeightedStratumFlameSeries.begin(),
                            allSpeciesWeightedStratumFlameSeries.end(),
                            [s] (FlameSeries fs) {return fs.level() == s.level();});
        if (iter == allSpeciesWeightedStratumFlameSeries.end())
          continue; //no FlameSeries for stratum s
        //if there is a connection then combine the flames
        if (find(flameConnections.begin(), flameConnections.end(), s.level()) < flameConnections.end() ||
            forest_.verticalAssociation(s.level(),strat.level())) {
          //combine the flames
          incidentFlames = combineFlames(incidentFlames, (*iter).flames(),
                                         flameWeightedWind,
                                         slope(),
                                         firelineLength_);
        }
      }
    }

    //initialise (with zeros) vectors to hold plant flame lengths depth ignited, origins and temperatures.
    std::vector<double> speciesWeightedFlameLengths(ffm_settings::maxTimeSteps,0);
    std::vector<double> speciesWeightedFlameDepths(ffm_settings::maxTimeSteps,0);
    std::vector<Pt> speciesWeightedFlameOrigins(ffm_settings::maxTimeSteps,Pt(0,0));
    std::vector<double> speciesAndFlameWeightedFlameTemps(ffm_settings::maxTimeSteps,0);

    //initialise Pt that will hold the species weighted sum of flame origins of the largest flame from the
    //most influential of the scenarios for each species
    Pt speciesWeightedFlameOrigin(0,0);

    //double to hold species weighted ignition time. This is used for pre-heating calc
    double speciesWeightedIgnitionTime = 0;

    double stratumWindSpeed = forest_.windProfile(incidentWindSpeed_, strat.avMidHt(), includeCanopy);

    //bool for whether connection
    bool connection = false;

    if (strat.includeForIgnition()) {
      //first loop over the species - plant ignition
      for (const Species& spec : strat.allSpecies()) {

        double comp = spec.composition();

        //initialise ignition path for this species. This will eventually hold the ignition
        //path for the most influential scenario
        IgnitionPath speciesIgnitionPath;

        //loop over ignition point scenarios
        for (int ignitPtScenario = -2; ignitPtScenario <= 2; ++ignitPtScenario) {

          //compute scenario initial ignition point, make sure this is not below the surface
          Pt iPt = spec.crown().pointInBase( 0.25*ignitPtScenario*spec.width());
          if (iPt.y() < iPt.x()*tan(slope()))
            iPt = Pt(iPt.x(), iPt.x()*tan(slope()));

          //compute ignition path for this scenario
          IgnitionPath iPath = computeIgnitionPath(incidentFlames,
              PLANT_IGNITION_RUN,
              preHeatingFlames,
              preHeatingEndTime,
              strat.level(),
              spec,
              0,
              stratumWindSpeed,
              iPt);

          if (iPath.hasSegments()) {
            // ignition occurred

            if (speciesIgnitionPath.hasSegments()) {
              // there was a prior path with ignition...
              // check to see if this ignition scenario has a longer flame length than any of the previous ones.
              // and replace stored IgnitionPath if necessary. Note we compare segment lengths
              if (ffm_numerics::gt(iPath.maxSegmentLength(), speciesIgnitionPath.maxSegmentLength()))
                speciesIgnitionPath = iPath;

            } else {
              // no prior path with ignited segments so save this one
              speciesIgnitionPath = iPath;
            }

          } else {
            // ignition did not occur...
            // if the prior path also didn't have ignition, keep the one with the max temperature
            if (!speciesIgnitionPath.hasSegments() && ffm_numerics::gt(iPath.maxPreIgnitionTemp(), speciesIgnitionPath.maxPreIgnitionTemp())) {
              speciesIgnitionPath = iPath;
            }
          }

        }//end of loop over ignition scenarios

        //add the plant ignition path to ignitionRun
        if (speciesIgnitionPath.hasSegments() || speciesIgnitionPath.hasPreIgnitionData()) {
          ignitionRun.addPath(speciesIgnitionPath);

          if (speciesIgnitionPath.hasSegments()) {
            //add component of species weighted ignition time
            speciesWeightedIgnitionTime += (speciesIgnitionPath.timeToIgnition() +
                speciesIgnitionPath.timeIgnitionToMaxFlame()) * comp;
            //order the vector of ignited segments according to flamelength
            speciesIgnitionPath.sortSegments();

            //loop over segments and add species-weighted flame information
            unsigned i = 0;
            for (const Seg& sg : speciesIgnitionPath.ignitedSegments()){
              if (ffm_numerics::leq(sg.length(), 0))
                break; //because the segments are sorted max to min
              double tmpFlameLength = spec.flameLength(sg.length());
              //if flame from any segment burns out past the edge of the plant then set connection to true
              if (!connection &&
                  sg.start().x() +
                  tmpFlameLength*cos(windEffectFlameAngle(tmpFlameLength, stratumWindSpeed, slope())) > 0.5*spec.width()
                 )
                connection = true;
              //add (species weighted) flame info to vector of flame lengths
              speciesWeightedFlameLengths.at(i) += comp*tmpFlameLength;
              speciesWeightedFlameDepths.at(i) += comp*sg.length();
              speciesWeightedFlameOrigins.at(i) += comp*sg.start();
              speciesAndFlameWeightedFlameTemps.at(i) += comp*tmpFlameLength *
                (spec.isGrass() && strat.level() == Stratum::NEAR_SURFACE ?
                 ffm_settings::grassFlameDeltaTemp : ffm_settings::mainFlameDeltaTemp);
              ++i;
            }

            //add the composition weighted origin from the largest (first after sorting) flame length
            speciesWeightedFlameOrigin += comp*speciesIgnitionPath.ignitedSegments().front().start();
          }
        }

      } //end of first loop over species - plant ignition
    }

    //If there is a non-trivial vector of species-weighted flame data from the plant ignition
    //calculation then we continue on to the stratum ignition calc
    //note that the flame lengths were sorted so we only check the first one
    if (!ffm_numerics::leq(speciesWeightedFlameLengths.front(),0)) {
      //finish the flame weighting
      unsigned i = 0;
      for (auto& temp : speciesAndFlameWeightedFlameTemps) {
        if (ffm_numerics::leq(speciesWeightedFlameLengths.at(i),0))
          break; //because the segments were ordered max to min
        temp /= speciesWeightedFlameLengths.at(i);
        ++i;
      }

      //do lateral merging of combined flame timeseries (Eq 5.74, 5.75) eg Fuels!G132
      for (double& fl : speciesWeightedFlameLengths) {
        if (ffm_numerics::leq(fl,0)) break; //because ignited segments were ordered max to min
        fl = laterallyMergedFlameLength(fl, firelineLength_, strat.avWidth(), strat.modelPlantSep());
      }

      //make a FlameSeries to hold species-weighted plant flames
      FlameSeries speciesWeightedPlantFlames(strat.level());
      for (unsigned i = 0; i < (ffm_settings::maxTimeSteps); ++i) {
        if (ffm_numerics::leq(speciesWeightedFlameLengths.at(i),0)) break; //because ordered max to min
        speciesWeightedPlantFlames.addFlame(Flame(speciesWeightedFlameLengths.at(i),
                                                  windEffectFlameAngle(speciesWeightedFlameLengths.at(i),
                                                                       stratumWindSpeed, slope()),
                                                  speciesWeightedFlameOrigins.at(i),
                                                  speciesWeightedFlameDepths.at(i),
                                                  speciesAndFlameWeightedFlameTemps.at(i)));
      }

      //accumulate pre heating start time which is determined by the plant ignition sequences
      cumulativePreHeatingStartTime += speciesWeightedIgnitionTime;
      //set preHeatingEndTime for the next pass through strata loop
      //the current strata will supply direct heating to the next
      //strata so the preheating will stop at this time.
      preHeatingEndTime = cumulativePreHeatingStartTime;

      //if strat is canopy then compute distance out along stratum for which bottom edge of canopy is heated
      double canopyHeatingDistance = 0;
      if (strat.level() == Stratum::CANOPY) {
        //line to represent the bottom of the canopy
        double canopyBottom = strat.avBottom();
        Line canopyLine(Pt(0.0, canopyBottom), slope());

        // loop over all the flame series which are ordered from bottom stratum upwards
        for (std::vector<FlameSeries>::const_iterator it = allSpeciesWeightedStratumFlameSeries.begin();
             it != allSpeciesWeightedStratumFlameSeries.end();
             ++it) {
          Flame f = (*it).flames().front(); //first and largest flame because flameseries were sorted
          Ray plume = f.plume();

          // If the plume is hot enough at the point where it intersects bottom of the canopy
          // then update the canopyHeatingDistance.
          Pt pt;
          plume.intersects(canopyLine, pt);

          if (ffm_numerics::lt(pt.y(), plume.start().y())) {
            // Assuming that flame angles are always above the horizontal,
            // an intersection point Y ordinate less than the flame origin
            // Y ordinate indicates that the origin is above the bottom edge
            // of the canopy. In this case just use the flame origin as the
            // intersection point.
            pt = plume.start();
          }

          if (f.plumeTemperature(pt, weather().airTempC()) >= ffm_settings::minTempForCanopyHeating)
            canopyHeatingDistance = std::max(canopyHeatingDistance, pt.x());
        }
      }

      //reset vectors that hold species weighted flame information
      speciesWeightedFlameLengths = std::vector<double>(ffm_settings::maxTimeSteps,0);
      speciesWeightedFlameDepths = std::vector<double>(ffm_settings::maxTimeSteps,0);
      speciesAndFlameWeightedFlameTemps = std::vector<double>(ffm_settings::maxTimeSteps,0);
      speciesWeightedFlameOrigins = std::vector<Pt>(ffm_settings::maxTimeSteps, Pt(0,0));

      //second loop over species - stratum ignition
      for (const auto& spec : strat.allSpecies()) {

        double comp = spec.composition();

        //make a stratum polygon.
        std::vector<Pt> verts;
        double xx = strat.modelPlantSep() - 0.5*strat.avWidth();
        verts.push_back(Pt(xx, strat.avTop() + xx*tan(slope())));
        verts.push_back(Pt(xx, strat.avBottom() + xx*tan(slope())));
        xx += 10000;
        verts.push_back(Pt(xx, strat.avBottom() + xx*tan(slope())));
        verts.push_back(Pt(xx, strat.avTop() + xx*tan(slope())));
        Poly stratumPoly(verts);

        //make a species based on this polygon
        Species bigSpecies(comp,
                           spec.name(),
                           stratumPoly,
                           spec.liveLeafMoisture(),
                           spec.deadLeafMoisture(),
                           spec.propDead(),
                           spec.silFreeAshCont(),
                           spec.ignitTemp(),
                           spec.leafForm(),
                           spec.leafThick(),
                           spec.leafWidth(),
                           spec.leafLength(),
                           spec.leafSep(),
                           spec.stemOrder(),
                           spec.width(),
                           std::max(spec.clumpSep(), strat.modelPlantSep() - strat.avWidth()));

        //initialise an ignition path for the stratum ignition
        IgnitionPath speciesIgnitionPath;

        //compute the initial point for the ignition
        Ray r(speciesWeightedFlameOrigin, speciesWeightedPlantFlames.flames().front().angle());
        Pt iPt;
        if (!r.intersects(stratumPoly, iPt)) {
          ;//what happens here? nothing, we are left with an empty ignition path
        } else {
          //compute ignition path, note no preheating here
          speciesIgnitionPath = computeIgnitionPath(speciesWeightedPlantFlames.flames(),
                                                    !PLANT_IGNITION_RUN,
                                                    std::vector<PreHeatingFlame>(),
                                                    0,
                                                    strat.level(),
                                                    bigSpecies,
                                                    canopyHeatingDistance,
                                                    stratumWindSpeed,
                                                    iPt);
        }

        if (speciesIgnitionPath.hasSegments()) {
          ignitionRun.addPath(speciesIgnitionPath);

          //add component of species weighted ignition time
          // speciesWeightedIgnitionTime += (speciesIgnitionPath.timeToIgnition() +
          //                              speciesIgnitionPath.timeIgnitionToMaxFlame()) * comp;
          //order the vector of ignited segments according to flamelength
          speciesIgnitionPath.sortSegments();

          int i = 0;
          for (const Seg& sg : speciesIgnitionPath.ignitedSegments()){
            double flameLen = bigSpecies.flameLength(sg.length());
            speciesWeightedFlameLengths.at(i) += comp*flameLen;
            speciesWeightedFlameDepths.at(i) += comp*sg.length();
            speciesAndFlameWeightedFlameTemps.at(i) += comp*flameLen*
              (bigSpecies.isGrass() && strat.level() == Stratum::NEAR_SURFACE ?
               ffm_settings::grassFlameDeltaTemp : ffm_settings::mainFlameDeltaTemp);
            speciesWeightedFlameOrigins.at(i) += comp*sg.start();
            ++i;
          }
        }

      }//end of second loop over species - stratum ignition

      //finish the flame weighting
      i = 0;
      for (auto& temp :  speciesAndFlameWeightedFlameTemps) {
        if (ffm_numerics::almostZero(speciesWeightedFlameLengths.at(i)))
          break; //because the segments were ordered max to min
        temp /= speciesWeightedFlameLengths.at(i);
        ++i;
      }

      //make a species weighted flame series from the stratum calculations. This flame series will
      //become part of the incident flames for the next stratum
      FlameSeries speciesWeightedStratumFlames(strat.level());
      for (unsigned i = 0; i < (ffm_settings::maxTimeSteps); ++i) {
        if (ffm_numerics::leq(speciesWeightedFlameLengths.at(i),0)) break; //because ordered max to min
        speciesWeightedStratumFlames.addFlame(Flame(speciesWeightedFlameLengths.at(i),
                                                    windEffectFlameAngle(speciesWeightedFlameLengths.at(i),
                                                                         stratumWindSpeed, slope()),
                                                    speciesWeightedFlameOrigins.at(i),
                                                    speciesWeightedFlameDepths.at(i),
                                                    speciesAndFlameWeightedFlameTemps.at(i)));
      }
      //compare maximum flame lengths from the species weighted plant flames and the
      //species weighted stratum flames to determine which of these will be used to
      //form the incdent flames for the higher strata
      const FlameSeries& speciesWeightedFlames = (!speciesWeightedStratumFlames.isNull() &&
                                                  speciesWeightedStratumFlames.flames().at(0).flameLength() >=
                                                  speciesWeightedPlantFlames.flames().at(0).flameLength()
                                                  ?
                                                  speciesWeightedStratumFlames : speciesWeightedPlantFlames);
      //push the species weighted flame series onto the vector from which the incident flames will be
      //computed for the next stratum
      allSpeciesWeightedStratumFlameSeries.push_back(speciesWeightedFlames);
      //add the relevant preheating info to the vector of pre heating flames. Note that the start
      //time of the preheating flame is computed from the plant ignition sequences whereas the
      //preheating flame itself comes from the stratum ignition sequences
      double mfl = speciesWeightedFlames.meanFlameLength();
      PreHeatingFlame phf = PreHeatingFlame(strat.level(),
                                                 Flame(mfl,
                                                       windEffectFlameAngle(mfl,stratumWindSpeed,slope()),
                                                       speciesWeightedFlames.meanOrigin(),
                                                       speciesWeightedFlames.meanDepthIgnited(),
                                                       speciesWeightedFlames.meanDeltaTemperature()),
                                                 cumulativePreHeatingStartTime,
                                                 cumulativePreHeatingStartTime
                                                 + speciesWeightedFlames.nonNullCount()
                                                 *ffm_settings::computationTimeInterval);

      preHeatingFlames.push_back(phf);
      //check whether largest (ie first after sorting) species weighted stratum flame is longer than
      //the largest species weighted plant flame and if so set connection. This is part of the
      //calculation from the spreadsheet to determine whether or not a stratum can ignite an upper stratum
      //see for example Fuels!B266 and B269
      if (!connection && !speciesWeightedStratumFlames.isNull() &&
          speciesWeightedStratumFlames.flames().at(0).flameLength() >
          speciesWeightedPlantFlames.flames().at(0).flameLength())
        connection = true;
    }//end of stratum ignition computation

    if (connection)
      flameConnections.push_back(strat.level());

  } //end of loop over strata_

  //compute combined flames for all strata, including canopy, from surface flames and strata flames
  //only include lower strata that have a conection with canopy, ie vertical association etc
  std::vector<Flame> allStrataCombinedFlames(surfaceFlames);

  if (!allSpeciesWeightedStratumFlameSeries.empty()) {

    //first we need to compute a flame-weighted wind for use in the flame combination
    double flameSum = surfaceFlames.at(0).flameLength();
    double flameWeightedWind = surfWindSpeed*flameSum;
    for (const auto& s : strata()) {
      //find the flameseries from stratum s
      auto i = find_if(allSpeciesWeightedStratumFlameSeries.begin(),
                       allSpeciesWeightedStratumFlameSeries.end(),
                       [s](FlameSeries fs){return fs.level() == s.level();});
      if (i == allSpeciesWeightedStratumFlameSeries.end())
        continue; //no output for stratum s
      //if there is a connection then add the winds
      if (find(flameConnections.begin(), flameConnections.end(), s.level()) < flameConnections.end() ||
          forest_.verticalAssociation(s.level(),Stratum::CANOPY) ||
          s.level() == Stratum::CANOPY) {
        double flen = (*i).cappedMaxFlameLength();
        double w = forest_.windProfile(incidentWindSpeed_, s.avMidHt(), includeCanopy);
        flameWeightedWind += flen * w;
        flameSum += flen;
      }
    }
    flameWeightedWind = flameSum > 0 ? flameWeightedWind / flameSum : 0;

    //now compute the combined flames
    for (const auto& s : strata()) {
      //find the flameseries for stratum s
      auto i = find_if(allSpeciesWeightedStratumFlameSeries.begin(),
                       allSpeciesWeightedStratumFlameSeries.end(),
                       [s] (FlameSeries fs) {return fs.level() == s.level();});
      if (i == allSpeciesWeightedStratumFlameSeries.end())
        continue; //no FlameSeries for stratum s
      //if there is a connection then combine the flames
      if (find(flameConnections.begin(), flameConnections.end(), s.level()) < flameConnections.end() ||
          forest_.verticalAssociation(s.level(),Stratum::CANOPY) ||
          s.level() == Stratum::CANOPY) {
        //combine the flames
        allStrataCombinedFlames = combineFlames(allStrataCombinedFlames, (*i).flames(),
                                                flameWeightedWind,
                                                slope(),
                                                firelineLength_);
      }
    }
  }
  ignitionRun.combinedFlames(allStrataCombinedFlames);
  return ignitionRun;
}

/*!\brief Computes an IgnitionPath through the leaf crown of a Species
  \param incidentFlames This is the vector (time series) of flames that will
  be applied to the crown of the Species spec. In the case plantFlameRun == true,
  ie plant ignition, incidentFlames will be a combination of flames that come
  from surface computations and flames that come from lower strata. If
  plantFlameRun == false, ie stratum ignition, then the incident flames are
  derived from the plant ignition run which will have already been done.
  \param plantFlameRun If true then the method is computing a plant ignition path.
  If false then a stratum ignition path is being computed
  \param preHeatingFlames The pre-heating flames are derived from averaged values of
  flames in the surface and lower strata. They produce heating that occurs before the
  direct heating from the stratum immediately below.
  \param preHeatingEndTime The pre-heating ceases at preHeatingEndTime, the time
  that the direct heating starts. This may be earlier than the end time of any or
  all of the pre-heating flames.
  \param level The Stratum::LevelType in which the ignition path is being computed.
  \param spec This is the Species object through which the ignition path is being computed.
  In the case that this is a stratum ignition run then spec will be a Species object
  with a modified crown and a modified clump separation attribute, and will represent
  a stratum made up of this single species.
  \param canopyHeatingDistance If plantFlameRun == false and level == CANOPY then
  the flame residence time is reduced to ffm_settings::reducedCanopyFlameResidenceTime
  for points whose x-coordinate exceed canopyHeatingDistance.
  \param windSpeed The wind speed for the computation. Typically this is the wind speed
  at mid height of the stratum under consideration.
  \param initialPt A point on the boundary of the crown of spec which forms the initial
  point of the sequence of ignited segments that is being computed.
  \return An ignitionPath object representing the path of ignition through spec

The computation produces a sequence (vector) of ignited segments in the crown of spec.
It loops over an indeterminate number of time steps, but no more than ffm_settings::maxTimeSteps
steps after ignition first occurs. At each time step after initial ignition, the
computation produces a segment representing the part of the crown that is burning at that
time. The computation ends when the plant flames and incident flames are both extinguished,
when the ignition path has burnt completely through the crown, or when
ffm_settings::maxTimeSteps steps have elapsed. At any time step there are two sources of
heating being applied to the crown: the appropriate element of the incidentFlames and the
plant flame that was computed at the previous time step. The computation determines,
at each time step, a potential path of ignition through the crown beginning at the end
point of the segment computed in the previous time step. This path is determined
by the incident and the plant flames, and is chosen so as to maximise the length of
the segment of the crown that can potentially ignite during the time step. The
length of this potentially ignitable segment is computed using the ignition temperature
of spec, flame angles, the geometry of the crown, and plume temperature calculations.
However the portion of this segment that will actually ignite during the current time
step will also depend on the ignition delay time of the species, as well as the drying
that has occurred as a result of the pre-heating flames and the incident and plant flames
from the previous time steps. To compute the distance along the potential ignition path
for which ignition occurs during the current time step, the path is divided into
ffm_settings::numPenetrationSteps equal segments, and the end point (the test point) of
each of these segments is tested for ignition during that time step. The test point is
subjected to drying from the preHeatingFlames flames, from the incidentFlames, and from the
plant flames from previous time steps. The magnitude of the drying, along with the ignition
delay time at the test point, determines whether or not ignition occurs during that time step.
Drying is conceptualised as a multiplicative reduction factor applied to the ignition delay
time of the species. If a point within the crown is subjected to a temperature T for a time t
then this produces a drying factor of 1 - t/idt(T) where idt(T) is the ignition delay time
of the species at temperature T. This is applied repeatedly at the test point for the
preHeatingFlames, the incidentFlames and the plant flames, and the resultant drying factor
is the product of each of these results. The test point is said to ignite when the product
of its ignition delay time and the drying factor is less than the length of a time step,
ffm_settings::computationTimeInterval. If the test point is found to ignite during the
time step then the next of the test points along the potential ignition path is tested
in the same fashion, and so on. The distance along the potential ignition path that is
actually ignited, and therefore the end point of the ignited segment for that time step,
is so determined. The start point of the ignited segment depends on the flame residence
time and the previously computed segments.
Having computed the ignited segment, the computation proceeds to the next time step.
It ceases when ffm_settings::maxTimeSteps have elapsed, when both the incident and plant
flames have extinguished, or when there is no further potential path of ignition through
the plant, ie when the ignition path has reached the top or side of the plant.
*/
IgnitionPath Location::computeIgnitionPath(const std::vector<Flame>& incidentFlames,
                                           const bool& plantFlameRun,
                                           std::vector<PreHeatingFlame> preHeatingFlames,
                                           const double& preHeatingEndTime,
                                           const Stratum::LevelType& level,
                                           const Species& spec,
                                           const double& canopyHeatingDistance,
                                           const double& windSpeed,
                                           const Pt& initialPt) const {

  //initialise ignition path
  IgnitionPath iPath(plantFlameRun ? IgnitionPath::PLANT_PATH : IgnitionPath::STRATUM_PATH,
                     level, spec, -99);

  //pop the last pre-heating flame off the vector of pre-heating flames because that level will
  //provide the direct heating
  if(!preHeatingFlames.empty())
    preHeatingFlames.pop_back();

  //initialise a vector to hold plant flames
  //this saves having to recompute them from iPath multiple times
  std::vector<Flame> plantFlames;
  plantFlames.reserve(ffm_settings::maxTimeSteps);

  bool isGrass = spec.isGrass() && level == Stratum::NEAR_SURFACE;
  Pt iPt = initialPt;
  bool ignition = false;
  int safetyCounter = 1;

  //we loop over an indeterminate number of time steps, but no more
  //than maxTimeSteps counted from when ignition occurs. This is why
  //we use safetyCounter instead of timeStep in the loop condition.

  for (int timeStep = 1; safetyCounter <= (ffm_settings::maxTimeSteps) ; ++timeStep) {

    //for plant flame, and only if required, we modify wind speed by
    //reducing it by speed of flame progression
    double modifiedWindSpeed = windSpeed;
    if (!plantFlameRun && iPath.hasSegments()) {
      int sz = iPath.numSegments();
      if (sz == 1)
        modifiedWindSpeed = windSpeed -
          std::max(0.0,
                   iPath.ignitedSegment(0).end().x() - initialPt.x())/ffm_settings::computationTimeInterval;
      else
        modifiedWindSpeed = windSpeed -
          std::max(0.0,iPath.ignitedSegment(sz-1).end().x() - iPath.ignitedSegment(sz-2).end().x())/
          ffm_settings::computationTimeInterval;
    }

    //get plant flame from previous time step
    Flame plantFlame = plantFlames.empty() ? Flame() : plantFlames.back();
    //get incident flame
    Flame incidentFlame = timeStep <= incidentFlames.size() ? incidentFlames.at(timeStep - 1) : Flame();

    //if no plant flame or incident flame decide what to do
    //we allow for the possibility that incident flame may flare up later
    //though this is not possible under current model because of reordering
    if (plantFlame.isNull() && incidentFlame.isNull()) {
      if (timeStep <= incidentFlames.size())
        continue;
      else
        break;
    }

    //compute potential ignition distance for plant flame
    double maxPlantPath = 0;
    if (!plantFlame.isNull()) {
      Ray r(iPt, plantFlame.angle());
      double intsctLen = r.intersectionLength(spec.crown());
      double ignitLen = plantFlame.inversePlumeTemperature(spec.ignitionTemp(),weather_.airTempC());
      maxPlantPath = std::min(r.intersectionLength(spec.crown()),
                              plantFlame.inversePlumeTemperature(spec.ignitionTemp(),weather_.airTempC()));
    }

    //compute incident flame characteristics. maxIncidentPath is the length of the segment in the
    //plant that could potentially be ignited by the incident flame, based on iPt, geometry of the
    //plant crown, plume temperature and plant ignition temperature
    double maxIncidentPath = 0;
    Pt incidentFlameOrigin;
    Line surfaceLine(Pt(0,0), slope());
    if (!incidentFlame.isNull()) {
      if (plantFlameRun) {
        //if possible set the incidentFlameOrigin to a point on the surface such that a ray starting
        //at that point with angle incidentFlame.angle() will pass through iPt. If this is not possible
        //return false, but this should never occur because flame angles are always greater than the slope.
        if (!surfaceLine.originOnLine(incidentFlame.angle(), iPt, incidentFlameOrigin))
          ; //this should not happen
      } else
        incidentFlameOrigin = incidentFlame.origin();
      Ray r(iPt, incidentFlame.angle());
      double pathDistance = r.intersectionLength(spec.crown());
      double ignitionDistance =
        std::max(0.0,
                 incidentFlame.inversePlumeTemperature(spec.ignitionTemp(),weather_.airTempC())
                 - (iPt - incidentFlameOrigin).norm());
      maxIncidentPath = std::min(pathDistance, ignitionDistance);
    }

    //if there is any more of the plant to burn then compute ignition for the next time step
    Pt ePt = iPt;
    if (ffm_numerics::gt(maxIncidentPath, 0.0) || ffm_numerics::gt(maxPlantPath, 0.0)) {
      //the direction and max possible extent of the next ignition segment is determined by
      //whichever path has the greatest length
      double pathLength = maxPlantPath > maxIncidentPath ? maxPlantPath : maxIncidentPath;
      double pathAngle = maxPlantPath > maxIncidentPath ? plantFlame.angle() : incidentFlame.angle();

      int i = 0;
      for (auto& phf : preHeatingFlames) {
        if (!phf.flame().isNull()) {
          Pt originPt;
          Line tmpLine(phf.flame().origin(), slope());
          if (!tmpLine.originOnLine(phf.flame().angle(), ePt, originPt))
            ;

          // This line does not update the PreHeatingFlame's Flame field as intended.
          // Instead it is modifying a copy which is then silently discarded, leaving
          // the origin point at (0,0).
          //
          //preHeatingFlames.at(i).flame().origin(originPt);

          // Hack fix for above problem: create a new PreHeatingFlame object - MB
          Flame flame = phf.flame();
          flame.origin(originPt);
          preHeatingFlames.at(i) = PreHeatingFlame(phf.level(), flame, phf.startTime(), phf.endTime());
        }
        i++ ;
      }
      //the possible ignition distance is divided into numPenetrationSteps segments and we test each
      //segment in turn for ignition
      for(int step = 1; step <= ffm_settings::numPenetrationSteps; ++step) {
        Pt testPt = ePt + (pathLength/ffm_settings::numPenetrationSteps)*Pt(cos(pathAngle),sin(pathAngle));
        double dryingFactor = 1;
        double dryingTemp;

        //compute the drying at testPt from preheating flames. NOTE that we have already popped the last
        //of the preheating flames off the vector because that level will provide the direct heating
        for (const auto& phf : preHeatingFlames){
          if (!phf.flame().isNull()) {
            //compute the temperature at the test point from the drying flame
            double distToFlame = (testPt - phf.flame().origin()).norm();
            dryingTemp = phf.flame().plumeTemperature(distToFlame, weather_.airTempC());
            //compute the IDT at the test point
            double idt = spec.ignitionDelayTime(dryingTemp) *
              (isGrass ? ffm_settings::grassIDTReduction : 1.0);

            double duration = phf.duration(preHeatingEndTime);
            dryingFactor *= std::max(0.0, 1 - duration / idt);

            if (iPt == initialPt && timeStep == 1 && step == 1) {
              iPath.addPreIgnitionData( PreIgnitionData::preheating(
                                      phf.flame().flameLength(), phf.flame().depthIgnited(),
                                      distToFlame, dryingFactor, dryingTemp, duration) );
            }
          }

          if (dryingFactor <= 0)
            break;
        }

        //drying from the incident flames, but only if testPt is not already completely dry
        if (dryingFactor > 0) {
          int numDryingSteps = std::min(timeStep - 1, static_cast<int>(incidentFlames.size()));
          for(int i = 1; i <= numDryingSteps; ++i){
            Flame dryingFlame = incidentFlames.at(i - 1);
            if (dryingFlame.isNull()) continue;
            Pt dryingFlameOrigin;
            if (plantFlameRun) {
              if (!surfaceLine.originOnLine(dryingFlame.angle(), iPt, dryingFlameOrigin))
                ; //this should not happen
            }   else
              dryingFlameOrigin = dryingFlame.origin();
            dryingTemp = dryingFlame.plumeTemperature((testPt - dryingFlameOrigin).norm(), weather_.airTempC());
            double dryingIDT = spec.ignitionDelayTime(dryingTemp)*
              (isGrass ? ffm_settings::grassIDTReduction : 1.0);
            dryingFactor *= std::max(0.0,1 - ffm_settings::computationTimeInterval / dryingIDT);
          }
        }

        //drying from the plant flames, but only if testPt is not already completely dry
        if (dryingFactor > 0) {
          for (const Flame& dryingFlame : plantFlames){
            if (dryingFlame.isNull()) continue;
            dryingTemp = dryingFlame.plumeTemperature(testPt, weather_.airTempC());
            double dryingIDT = spec.ignitionDelayTime(dryingTemp)*
              (isGrass ? ffm_settings::grassIDTReduction : 1.0);
            dryingFactor *= std::max(0.0, 1 - ffm_settings::computationTimeInterval / dryingIDT);
          }
        }

        //compute temperatures at test Pt from incident flame and plant flame
        double distToIncidentFlame = (testPt - incidentFlameOrigin).norm();
        double incidentTemp = incidentFlame.plumeTemperature(distToIncidentFlame, weather_.airTempC());
        double plantTemp = plantFlame.plumeTemperature((testPt - plantFlame.origin()).norm(), weather_.airTempC());
        double maxTemp = std::max(incidentTemp, plantTemp);
        double idt = dryingFactor * spec.ignitionDelayTime(maxTemp)*
          (isGrass ? ffm_settings::grassIDTReduction : 1.0);

        if (iPt == initialPt && step == 1) {
          iPath.addPreIgnitionData(
              PreIgnitionData::incident(
                      incidentFlame.flameLength(), incidentFlame.depthIgnited(),
                      distToIncidentFlame, dryingFactor, incidentTemp, idt) );
        }

        //if ignition does not occur for testPt then break from loop over penetration steps
        if (idt > ffm_settings::computationTimeInterval || maxTemp < spec.ignitionTemp())
          break;

        //if we get here ignition has occurred, so reset end pt and continue
        ePt = testPt;
      }//end of loop over penetrations steps
    }

    //set startTimeStep  when plant first ignites
    if (!ignition && iPt != ePt) {
      iPath.startTimeStep(timeStep);
      ignition = true;
    }

    if (ignition){
      if (!iPath.hasSegments()) {
        iPath.addSegment(Seg(iPt,ePt));
        plantFlames.push_back(iPath.flame(modifiedWindSpeed, slope()));
      } else {
        //compute flame duration and hence start point of new segment
        int fd;
        if (!plantFlameRun && (level == Stratum::CANOPY) && (iPt.x() > canopyHeatingDistance))
          //flame residence time is reduced for stratum ignition in canopy if the canopy has not
          //been heated to sufficient temperature
          //fd = round(ffm_settings::reducedCanopyFlameResidenceTime/ffm_settings::computationTimeInterval);
          fd = ceil(ffm_settings::reducedCanopyFlameResidenceTime/ffm_settings::computationTimeInterval);
        else
          // fd = round(spec.flameDuration()
          //         /ffm_settings::computationTimeInterval);
          fd = ceil(spec.flameDuration()
                    /ffm_settings::computationTimeInterval);
        Pt segStart = iPath.numSegments() < fd ?
          iPath.ignitedSegments().front().start() : iPath.ignitedSegments().at(iPath.numSegments()-fd).end();
        //If the potential incident flame and plant flame path lengths are both zero and
        //the start point of the new segment is equal to its end point then break from the loop and
        //therefore end the ignition path computation.
        //There is a VERY slight problem with this in that later incident flames may have a different
        //angle and therefore a non-zero potential path. But this is unlikely and the alternative is
        //to keep the loop going for the full maxTimeSteps after ignition, which seems wasteful.
        if(!ffm_numerics::almostZero(maxIncidentPath) || !ffm_numerics::almostZero(maxPlantPath) || segStart != ePt) {
          iPath.addSegment(Seg(segStart, ePt));
          plantFlames.push_back(iPath.flame(modifiedWindSpeed, slope()));
        } else
          break; //from loop over time steps
      }
      //reset ignition point
      iPt = ePt;
      //increment counter if ignition has occurred
      ++safetyCounter;
    }
  }//end of loop over time steps
  return iPath;
}


text:
Q: I am working on an assignment about ordered list in java. We're not allowed to use sort so I made a solution but 2 numbers are outputted twice here's the code for MyArrayList
public void addLast(int number){
    if(!isFull())
        doubleTheArray();
        element[count++] = number;
}

and here's the code for MyOrderedList
public void add(int item){
    for(int i = 0; i < count; i++){

        for(int j = i+1; j < count; j++){

            if(element[i] > element[j]){
                item = element[i];
                element[i] = element[j];
                element[j] = item;
            }
        }
    }

    addLast(item);
}

This is the main method
public static void main(String [] args){
    MyOrderedList oList = new MyOrderedList();

    oList.add(3);
    oList.add(5);
    oList.add(2);
    oList.add(4);
    oList.add(7);
    oList.add(8);
    oList.add(10);
    System.out.println("Ordered list of items: "+ oList);
}

Can anyone help me find out what's wrong? We're not allowed to use sort so I made a solution but 2 numbers are outputted twice in the main method for MyOrderedList. We have to add the elements at the right location. Or in ascending order for add(item). isFull() is boolean method that doubles (doubleTheArray()) the array size if it is true.

redpajama_set_name: RedPajamaStackExchange


text:
Legislators Fail to Agree on DeWine's Gas Tax Proposal
Tue April 02, 2019 - Midwest Edition #7
A Sunday deadline passed with the Ohio Legislature and Senate failing to reach a deal on Gov. Mike DeWine's proposal for a gasoline tax increase.
Negotiations between the state House and Senate broke down March 29 and were adjourned until April 1, ensuring that the deadline of March 29 would not be met and the proposed tax increase would not take effect by July 1, as the governor wanted.
Ohio Department of Transportation Director Jack Marchbanks said in a statement to the Governor's Advisory Committee on Transportation and Infrastructure Feb. 5 that without suitable revenue enhancements, the DOT would run out of money for highway improvement projects July 1.
Marchbanks described the situation as a "fiscal cliff."
DeWine initially called on Ohio legislators to increase the state's gasoline tax by 18 cents and to index the tax to inflation to ensure ODOT's annual budget could properly maintain the state's transportation network.
The governor and House leaders announced a deal March 28 whereby the gas tax would rise 11 cents per gallon and the diesel fuel tax would rise by 20 cents per gallon, according to a report in the Dayton Daily News.
The original Senate plan increased the gas tax and diesel by only 6 cents per gallon, however, and by the time negotiations failed, the Senate was offering an 8.5-cent-per-gallon increase on gasoline and a 13-cent-per-gallon increase on diesel, the Daily News Reported.
The last time the state increased the gas tax was in 2003, when a two-cent increase for three consecutive years was approved. This tax was not indexed to inflation, and so, while ODOT's budget has essentially remained frozen, traffic volumes on ODOT roads are at "an all-time high," and with increased fuel efficiency, less gasoline is being purchased, Marchbanks said in his statement.
DeWine called the 11-cent compromise a way to "improve and maintain" safer roads and bridges across Ohio, the Daily News reported.
Gas Tax IncreaseOhioOhio Department of TransportationPolitics News
Gas Tax Increase Ohio Ohio Department of Transportation Politics News

redpajama_set_name: RedPajamaCommonCrawl


In eight different sections it leads you through the whole skiing experience – pre-ski exercises, equipment and of course the progression from basic turns right through to carving and handling steeper slopes. On top of that there's a list of extras including a top tips section and interviews on the psychology involved in overcoming fear; and the thinking behind modern ski methods.
It's the ultimate, the cream. It's what racers do on their days off. Off piste, free-ride, call it what you will, is unquestionably the best fun you can have on skis.
And, joy upon joy, modern kit has brought this avenue of the sport, once strictly a 'pros only zone,' within the grasp of anyone who can link parallel turns and has a little fire in their belly.
Off piste and free-ride , the 4th episode in the critically acclaimed SKIING SKILL SERIES, describes exactly what's involved. In over 1½ hours of material, the established team of presenter Peter Hart, training guru Sean Langmuir and former GB team racer Amanda Pirie break down the skills and techniques but equally importantly address all the psychological, tactical, physical and environmental issues of off piste skiing.
The DVD contains a heap of 'extras.' They call on the services of top guide Jim Kerr, an expert on mountain safety, and equipment connoisseur Eric Davies of Salomon. They have a chat with GB's most successful competitor Alain Baxter, while Al Morgan and Carrie Hainge from the omniscient Ski Club of Great Britain, offer mountains of advice on …everything!
Filmed in Chamonix, Europe's off piste capital, using the latest broadcast and point of view cameras, this program is lucid, informative and visually breathtaking.


text:
Q: How to send an email trigger for a new form response entry in a sheet I have a google form that creates new entries in a google sheet, I want to send an email to a specific person whenever a new entry is created, i.e. set up a trigger that actively tracks additions of entry in the sheet, how do I do this?

A: You should set up the Form Submit trigger:
function onFormSubmit(eventObject) {
    /*
     *  Code to extract & parse data from eventObject.response
     */
    MailApp.sendEmail(to, subject, body);
}

Additional References:
Form Submit Trigger Object
Form Response Object
MailApp sendEmail


text:
Completed Portraits: British 16th and 17th C, Portraits: British 18th C 17 comments Is this portrait of Gilbert Burnet a copy after Godfrey Kneller?
Photo credit: University of Aberdeen
See also University of Aberdeen's other portrait of Gilbert Burnet:
Gilbert Burnet (1643–1715), DD, Bishop of Salisbury (copy of a work by Godfrey Kneller), painted 1723, by Andrew McIlraith (attributed to)
Your Paintings link: http://www.bbc.co.uk/arts/yourpaintings/paintings/gilbert-burnet-16431715-dd-bishop-of-salisbury-copy-of-a-104957
The Sarah Hoadly portrait is not listed as a copy after Godfrey Kneller, but the portrait attributed to McIlraith is. Are both copies of a work by Kneller?
I found this by Kneller at the National Trust http://www.bbc.co.uk/arts/yourpaintings/paintings/gilbert-burnet-16431715-bishop-of-salisbury-171693 however the paintings do not look overly similar...
Jade Audrey King, 14 Sep 2015 Entry reviewed by Art UK
Completed, 17 Nov 2022 Outcome
This discussion is now closed. The attribution has been amended from Sarah Curtis Hoadly (1676–1742/1743) to Andrew McIlwraith (active 1715–1753), after John Riley. A note on the artwork page now indicates that this portrait (like ABDUA 30024 and ABDUA 30025; both also updated) is close in detail to a smaller portrait of Burnet in the National Portrait Gallery's collection and probably derives from a lost original.
Michael Liversidge, 14/09/2015 10:59
I think perhaps your painting is taken from the John Riley portrait of Burnet in the National Portrait Gallery, possibly based on the mezzotint engraving by John Smith issued in 1690. The Riley and your picture share the same self-satisfied look of a distinguished prelate of the period, and are equally over-fed. Also the 'rope' of the Garter mantle is arranged similarly.
The Riley is also discussed in the John Ingamells catalogue, 'Later Stuart Portraits 1685-1714' (London, NPG, 2009).
Neither the face, nor its colouring, in the Aberdeen picture are consistent with Godfrey Kneller.
John Riley original (National Portrait Gallery)
http://www.npg.org.uk/collections/search/portrait/mw00941/Gilbert-Burnet
http://www.npg.org.uk/collections/search/portraitLarge/mw43012/Gilbert-Burnet
I see the National Trust (Lyme, Cheshire) has another copy of the Riley image:
http://www.nationaltrustcollections.org.uk/object/499925
The rest of the Aberdeen portrait seems to follow the standard convention for a portrait of a Chancellor of the Order of the Garter, as for example in another picture by Godfrey Kneller of William Talbot, with the same Great Seal bag:
http://www.bbc.co.uk/arts/yourpaintings/paintings/william-talbot-16591730-as-bishop-of-salisbury-and-chancel99654
Oliver Perry, 14/09/2015 11:56
There is an engraving after a portrait of Burnet by Sarah Hoadly, in which he looks considerably older than he does in this painting:
http://www.npg.org.uk/collections/search/portrait/mw141746/Gilbert-Burnet?LinkID=mp07112&role=art&rNo=5
There's a reference to her portrait of Burnet in "The Lives of the Bishops of Winchester" (1827):
Many of her portraits would do honour to a professor of the art; particularly a pair of small whole lengths, of Mr. Hoadly just after, and of his brother just before, they were in orders; and another of Bishop Burnet in the family of ____ Michael, Esq. who married one of his daughters, from which Mr. Vertue made an excellent engraving.
https://books.google.co.uk/books?pg=PA234&lpg=PA234&dq;
Sarah Hoadly's husband, Benjamin Hoadly, himself became Bishop of Salisbury in 1723.
Barbara Bryant, 21/09/2015 13:59
Can we get a higher resolution image and/or detail and/or transcription of the signature/inscription on the stone plinth to the right side of the painting? This seems to show a date and some further information which will help.
Jade Audrey King, 28/09/2015 14:36
The collection have given permission for me to post a higher resolution image. Please find attached.
abd-ua-30638.jpg (6 MB)
Osmund Bullock, 29/09/2015 05:44
Well, there we are: not Sarah Hoadly, but Andrew McIlwraith 1723. And in fact nothing to do with Hoadly - it is another Riley copy, and not a good one. McIlwraith - he is, I think, always recorded with a 'W' in his name - was a Scottish portraitist (fl1715-1754) about whom little is known. A few portraits are recorded, including this one; he was a founding (1729) member of Edinburgh's Academy of St Luke (Scotland's original art academy, where Ramsay first trained), and 30 years after Burnet his skills had considerably improved: http://auctions.lyonandturnbull.com/auction-lot-detail/ANDREW-MCILWRAITH-(SCOTTISH-FL.-1715-1753)-PORTRAIT-OF-JAMES/388++++++266+/++170684
There seems to be much confusion on the part of the U. of Aberdeen and/or the PCF, and I'll try and clarify things now. There are many basic images of Burnet, but only two need concern us: those by or after Riley (c1689), and those by or after a later portrait by Mrs Hoadly.
As well as the two under discussion, Aberdeen in fact has a third portrait of Burnet, also attributed to Sarah Hoadly: http://www.bbc.co.uk/arts/yourpaintings/paintings/gilbert-burnet-16431715-dd-bishop-of-salisbury-104903 .
Now this clearly does relate to a number of prints at the NPG & BM which identify Hoadly as the original artist. Here's the 1723 Vertue one mentioned in Oliver's book extract: http://www.britishmuseum.org/research/collection_online/collection_object_details.aspx?objectId=3597731&partId=1&searchText=P,7.193
I doubt that this (U. of Aberdeen) portrait is the Hoadly original mentioned in the book as being with descendants c1827 (though the condition is so poor it's impossible to be sure), but it is certainly a version of it - and the only one on Your Paintings that is. The donor, John Farley Leith, MP for Aberdeen, lived 1808-1887.
The c1689 Riley type presumably all derive from a lost or untraced seated full-length version with all the accoutrements of office. The U. of Aberdeen's two other portraits of Burnet must be copies of this, as is another (ex-family) version at Crathes Castle, mis-attributed to Circle of Kneller: http://www.bbc.co.uk/arts/yourpaintings/paintings/gilbert-burnett-16431715-bishop-of-salisbury-196298
There is likely to have been, too, a smaller oval version by Riley, of which the NPG's one (currently on loan to the NT at Lyme Park) is a copy. This must also have been the source for John Smith's 1690 mezzotint (which identifies the artist) - all the other prints of this type probably derive from from that.
Gilbert Burnet's DNB article says there is an oil by or after J. Riley of c.1689–1691 at the Scottish NPG - perhaps the prime of one of these. But mysteriously this is not to be found on Your Paintings.
So in summary, the University of Aberdeen's portraits appear to have got mixed up. The one under discussion (104904) is not by Hoadly, but by McIlwraith - signed and dated - and a copy of Riley. The one attributed to McIlraith [sic](104957) is not a copy of Kneller, but of Riley, and is not by McIlwraith but by an unknown, superior hand. However, the damaged one also attributed to Hoadly (104903) is indeed by or after her. Kneller is a complete red herring, and the Crathes Castle portrait (196298) is also nothing to do with him, but another copy after Riley.
Before finalising this discussion, I think we do need to see the portrait by Riley that is listed as being at the Scottish National Portrait Gallery in the ODNB entry on Burnet. I have not found it on their website nor on Your Paintings (as Osmund noted). But I have determined that it is a 3/4 length so is probably the original of the ones at Aberdeen. I have contacted the SNPG and will expect to hear more about that version.
As I suspected, the signature and date on the version under discussion move everything forward considerably. Thank you Osmund for going through all the versions of Burnett's portrait.
The artist for this well-preserved portrait (ABDUA 30638) at the University of Aberdeen under discussion can now be attributed to Andrew McIlwraith and dated 1723. Given by John Farley Leith, who died in 1887, it must have come to the University in his lifetime or as a bequest after his death, so has been there for some considerable time.
The version at Crathes (National Trust for Scotland 51.960), http://www.bbc.co.uk/arts/yourpaintings/paintings/gilbert-burnett-16431715-bishop-of-salisbury-196298 which is on loan from the family of the sitter, is another copy.
The other exact version is also at the University of Aberdeen (ABDUA 30025) http://www.bbc.co.uk/arts/yourpaintings/paintings/gilbert-burnet-16431715-dd-bishop-of-salisbury-copy-of-a-104957. It gives rise to the question why the University bought this one when they already had the one by McIlwraith?
Well, I suppose the purchase could have taken place *before* they were given/left the McIlwraith version by Farley Leith (who also gave them their Sarah Hoadly one). It probably all makes more sense if purchase and gifts took place before 1860, when the modern University was created from Marischal College (Burnet's alma mater) and the older King's College. Prior to that they can be considered quite separate institutions, with their own benefactors and collections, now combined.
I'm afraid this now gets more complicated, Barbara.
I have found on the U. of Aberdeen's Library, Special Collections and Museums website all three of the portraits. This has higher-resolution images of them all (including the one we've received already), together with substantial further information and notes. I'm not quite clear why they didn't point us in this direction when Jade asked for the high-res, it would have saved a lot of time. I can't give the direct link, as the search times out. Go to http://digitool.abdn.ac.uk/ and do a simple search for "Painting Gilbert Burnet" in 'All collections' with the 'contains' box ticked. From the result page clicking on each will open a new page with access to (a) a zoomable higher-res image (click on thumbnail); and (b) the Full Museum Record (allegedly – in fact annoyingly truncated and indeed inaccurately transcribed in some cases).
1. (U. of A. ref ABDUA 30024 / PCF 104903) To get the first, simplest one out of the way – the damaged 'Sarah Hoadly'. The full record and the high-res image both reveal an inscription top right: 'Gilbert Burnet, D.D. BP. of Salisbury / painted by Mrs Hoadly'. So, yes, certainly Hoadly-type, as previously discussed: but even with this more detailed view I am undecided if it could be the original or not, such is the state it's in.
2. (ABDUA 30025 / PCF 104957) The closer-to-original copy of the Riley, with extensive inscription along the whole of the top. Here is given much detail of the circumstances of the McIlwraith acquisition: ' "...The portrait commission by Marischal was part of the College's attempt to distance itself from its Jacobite heritage after the defeat of 1715 and ingratiate itself with the Hanoverian dynasty. McIlwraith (fl1715-53) was an English painter resident in Edinburgh in the first half of the Eighteenth century. He was commissioned by Marischal College to copy this work from an original by John Riley...." Author: Morrison, John. Date: 2002...' There is more still in the Full Museum Record notes: 'Cost £69 6/- Scots in 1723 to be painted, there is a record in the university referring to the purchase and subsequent donation of the portrait. There are two versions of this work, some uncertainty as to which is which.' Indeed: surely they have this one and the next the wrong way round?
3. (ABDUA 30638 / PCF 104904) The portrait under discussion, apparently signed and dated 'And. McIlvraith / pinxit / 1723'. In fact the reading as 'McIlvraith' is probably wrong, the 'l' and 'w' of McIlwraith being conjoined. The Full Museum Record notes add: 'Copy, possibly late 19th c, contemporary with the frame; There are two versions of this work it is uncertain which is which...Conserved in 2006 by The Conservation Studio, 27 Duke Street, Leith (see object history file).'
So here is the nub of (some of) the confusion. It is suggested that this second version (no.3), recently restored, is a much later copy of McIlwraith's 1723 commissioned copy of Riley, which they therefore assume must (or may) be no.2 above. I agree that the condition of no.3 seems exceptionally good, and the conservator will obviously have had a closer view of the paintwork and canvas than is possible for us, but I must say I am surprised. It seems odd that a late C19th copyist would have added such an authentic-looking signature and date, neither of which is present on no.2. And looking in detail at the sitter's face on the high-res, I would say that it looks to me exactly as I would expect it to – a genuine and contemporary early C18th face, by a provincial hand and/or someone of relative youth and inexperience. I might add that the apparently high price of £69 6s is not at all: the 'pound Scots' was worth just one-twelfth of an English pound, so the cost was actually a mere £5-15s-6d. At the time in London even 'undercutting' artists like Aikman, Smibert (both Scots) and Gibson were asking ten or fifteen guineas for an original of this size (50x40 in.), to the annoyance of others who wanted to see 20 gns as the accepted minimum. Even allowing for provincial prices and it being a copy, I'd have thought an artist prepared to do it for under £6 would be considered cheap – and that fits with the limited skills on display here.
Unless there is some compelling evidence that it must be later, I would invoke Ockham's Razor. The simplest explanation, needing the fewest difficult assumptions, is probably the right one: this portrait, with a 1723 date and a McIlwraith signature, is indeed the 1723 commissioned copy by him.
I guess nothing is ever going to be simple, especially on Art Detective.
We could have done with all that info at the outset, but thanks Osmund for tracking it down.
Let's leave aside Hoadly for a while and concentrate on the Riley copies, for which, as I noted earlier, it would be useful to see the Scottish National Portrait Gallery's version.
I also agree that the McIlwraith's signature looks perfectly right for the period and therefore this version (i.e. the one we are discussing) is unlikely to be a 19C copy.
The signature certainly seems to have very similar mannerisms to the one on the back of the portrait of James Ker:
http://www.invaluable.com/auction-lot/andrew-mcilwraith-fl.1715-1753-portrait-of-jame-658-c-3eee62de45
Thanks for spotting that, Oliver - a very helpful bit of evidence indeed. Also worth noting the apparently excellent condition of the paint layer on that one.
Bendor Grosvenor, 06/11/2015 10:56
V good sleuthing everyone.
This discussion, "Is this portrait of Gilbert Burnet a copy after Godfrey Kneller?" has attracted 13 comments between September and November 2015. It concerns a ¾ length portrait in the collection of the University of Aberdeen. The key lengthy contribution to the discussion was made by Osmund Bullock (3 October), drawing on information from the University's website, which has since been restructured. Out of interest, it is worth noting that in an Edinburgh parish marriage register for 1715, available on Scotlands People website, Andrew Mcilwraith is described as a limner.
There are three versions of the portrait in the University's collection:
ABDUA 30024 / PCF 104903. In poor condition with holes. Artist given as Sarah Curtis Hoadly on Art UK. It is inscribed 'Gilbert Burnet, D.D. BP. of Salisbury / painted by Mrs Hoadly'.
SUGGESTION: keep existing attribution
ABDUA 30025 / PCF 104957. With a lengthy inscription at the top. Attributed to Andrew McIlwraith (active 1715–1753) on Art UK. The university website documents the original commission to McIlwraith in 1723 but this text actually relates to the following portrait.
SUGGESTION: keep existing attribution in the absence of other evidence.
ABDUA 30638 / PCF 104904. The work under discussion. Artist given as Sarah Curtis Hoadly (1676–1742/1743) on Art UK. Signed and dated: 'And. McIlvraith / pinxit / 1723'. The signature is close to that on another work, as noticed by Oliver Perry (October 2015). The documentation for this portrait, quoted on the university website as for the previous portrait, presumably relates to this one. SUGGESTION: Amend artist to: Andrew McIlwraith (fl. 1715-53).
All three versions seem to derive from a lost original by John Riley (1646-91), as suggested by Michael Liversidge (14 September).
SUGGESTION: All three could be described as 'After John Riley (1646-91)', as acknowledged on the university website. Perhaps include a note in any supporting text that they are close in detail to a smaller portrait of Burnet in the National Portrait Gallery's collection and probably derive from a lost original.
The version of this painting on the Art UK website as on loan from the family to the National Trust for Scotland at Crathes Castle, is described as "Godfrey Kneller (1646–1723) (circle of)".
SUGGESTION: amend to 'after John Riley (1646-91)'.
A recommendation on closing this discussion needs to come from the Group Leader, Bendor Grosvenor, unless his comment on 6 November 2015 was such a recommendation, and needs to be put to the collection for approval.
My previous comment was almost a year ago. Can we not take Bendor's comment on 6 November 2015 as a recommendation to close this 6 year old discussion and ask ArtUK now to obtain the collection's approval for closure?
The collection has been contacted by email.
Gilbert Burnet (1643–1715), DD, Bishop of Salisbury (after John Riley)
Andrew McIlwraith (active 1715–1753)
gift from John Farley Leith
ABDUA 30638
Close in detail to a smaller portrait of Burnet in the National Portrait Gallery's collection and probably derives from a lost original.


text:
DEPUTY PREMIER CLIFF CULLEN TOURS EXPANDING CANCERCARE SITE IN RUSSELL
Community Leadership will Help More People Access Care Closer to Home: Deputy Premier
Watch news conference
RUSSELL—With dedicated and ongoing support of the community, an initiative to improve access to chemotherapy in Russell continues to move forward, with construction expected to start on an expanded CancerCare Manitoba program space later this year, Deputy Premier Cliff Cullen announced here today as part of a tour of the site.
"Cancer touches the lives of nearly every Manitoban, either through a diagnosis of our own or for a loved one, so it is fitting the community has come together in Russell to support people on that journey," said Cullen. "This community-driven project means more people will be able to access the care they need, closer to home, for many years to come. Our government would like to recognize the leadership of the Expanding Community Cancer Care committee, the regional health authority and all of the people from Russell and nearby communities who have worked together to make this project a reality."
The Expanding Community Cancer Committee (ECCC) is fully funding all construction and equipment costs related to the expansion, estimated at $2.5 million, the deputy premier noted. To date, more than $1.8 million has been raised.
"We are delighted that this project, which will serve clients from 15 communities in our surrounding area, has received the green light," said Gloria Tibbatts, president, ECCC. "This is a testament to all the hard work and persistence by the committee, and all the support and financial contributions from our area to date. We thank everyone for their continued support because we could not have arrived at this stage without you."
Prairie Mountain Health has collaborated with health partners and stakeholders to plan for the necessary building parameters, construction costs and associated timelines for this important capital project. Prairie Mountain Health will contribute annual operating costs for the expansion including staffing and supplies.
"We are grateful for the hard work, dedication and commitment of the Expanding Community Cancer Committee and local support from the entire area," said Brian Schoonbaert, CEO, Prairie Mountain Health. "This much-needed new building addition, which will be about four times the size of the current unit, will better serve patients in this part of our health region, as well as provide a better physical environment for our staff to provide care."
"High-quality care which meets the highest standards is very important when we bring care closer to home," said Dr. Sri Navaratnam, president and CEO, CancerCare Manitoba. "This expansion is going to bring that to the community. I congratulate the community for their determination and perseverance to raise the funds to expand the space for cancer services in Russell."
Construction is expected to start this summer on the expansion of the chemotherapy unit expansion, which is located at the Russell Health Centre, the deputy premier noted. Once complete, the project will add another 2,300 square feet to the existing space.


Home Lifestyle Learn How to Get Joy and Pleasure!
Learn How to Get Joy and Pleasure!
Hobbies – is an activity that brings us joy and pleasure. That's what makes life interesting, full and rich. Hobbies are different: one person can please reading books, and the other – skydiving. And there are people who have a few hobbies, and they are real lucky. They will never be bored, because they always know how to entertain themselves. And there are people who are addicted to all little by little, like and like it, and it is, but really fun does not bring anything.
If you have not decided on a hobby, not disclosed in the Statement all your faces and talents and do not know what exactly you like, but today we want to tell you about the main types of hobbies. Read and feel every kind, and maybe your soul will meet, and you will realize that it is here that you would really like to do! In this case, do not put it in the closet, and look, try, learn and discover a new world.
Collecting. This hobby is very common, many people do something, so collect. This may be the brand, money, candy wrappers, pens, souvenirs, paintings, cards, beer mugs, recipes, weapons, badges, books, antiques. This list can be endless.
Sport. There is, where carousing. Maybe you want to dance or learn how to shoot a crossbow? Pleasure can deliver swimming, yoga and various games (badminton, tennis, golf). Active and extreme sports are very addictive, because people are beginning to feel the excitement, climb and jump adrenaline. This may be a parkour, mountaineering, kayaking, rafting, parachuting, mountain climbing, and so on.
Active hobbies. If you are an active person and like to move a lot, you can choose a hobby such as: roller skating, ice skating, skiing or biking; hiking and field trips; fishing, hunting or horse riding; travel, treasure hunting and archeology.
Quiet hobby. If you are a home, a quiet person and do not like to lead very active lifestyles, there are other kinds of hobbies for you. It – macramé, weaving, knitting, and everything related to crafts (sculpture, doll-making and hand-made items, painting on fabric or glass). It can be a hobby and intellectual orientation: writing poems and articles; checkers and chess; crossword puzzles; Reading books; Create photo and video clips, computer graphics.
It might also be watching good movies; Learning anything (astrology, numerology, palmistry, astronomy) and spiritual development (yoga, mantras, feng shui, meditation). Do not forget about floriculture, horticulture, floristry, flower arrangements, painting and photography.
You just have to sit or lie down, relax completely, look inside yourself and ask yourself the question: "Why seeks my soul, what she wants, that it pleases?". The answer is sure to come! It is likely you will want to learn to play a musical instrument or buy karaoke and start singing. Or you may want to explore and learn about the history or geography, and maybe you decide to go for a massage course.
It may well be that your desire is not quite normal, but do not stop yourself. The main thing – it is a creative process, the joy and enjoyment of life. There are many people who have unusual hobbies. For example, a Wolfgang Linder likes to swim with sharks, a lawyer Dunlap Hannon collects flags of different countries, and Frenchman Michel Pont collects fighter jets!
Do not be afraid to express themselves and start doing what you like!


Holy smokes Batman, that was a weekend for the record books!! Will and I have been photoing (yes, I know this is not a real verb!) just about every weekend this Fall. So when this "free" weekend came around, we decided to PACK it full of personal adventures. AND we aren't done yet! As I type, we just finished our first day of video education at The Wedding Filmmaker Workshop with The Herrintons!!
We started on Friday with a small birthday celebration at our house. We asked everyone to bring wine and cheese and it made us feel like we are adulting very well :)! First thing Saturday morning, we left for Charlotte, NC to meet Christian and B for pre-wedding celebrations! They are getting MARRIED in LESS THAN TWO WEEKS!! I can't believe how fast time is flying by! The ladies and gents split up and we (the ladies) headed for a downtown Davidson food tour. The food was ABSOLUTELY amazing and I'm still full from all the tastings!! I highly recommend it if you are in the Charlotte area! The gents went for go-cart racing!
If all of that didn't seem like a full enough weekend, we also went to the Packers game!! My dad, sister, and a few of my TN cousins have been going to a Packers game for a few years now and Will and I were close enough to come this year!! Seeing family even only for a short time is the best way to spend any day! The Packers didn't win against the undefeated Panthers BUT we still had a great time enjoying the tailgate and the game!! Plus the Panthers are a welcoming crowd and definitely live up to that southern hospitality!
Here are a few from our weekend! Enjoy!
Dad was very proud of his mini-grill!
Stretching before the game haha!