Date started: 09/13/00
Purpose: Encapsulates the aerodynamic forces
- ------------- Copyright (C) 2000 Jon S. Berndt (jsb@hal-pc.org) -------------
+ ------------- Copyright (C) 2000 Jon S. Berndt (jon@jsbsim.org) -------------
This program is free software; you can redistribute it and/or modify it under
- the terms of the GNU General Public License as published by the Free Software
+ the terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
- You should have received a copy of the GNU General Public License along with
+ You should have received a copy of the GNU Lesser General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA.
- Further information about the GNU General Public License can also be found on
+ Further information about the GNU Lesser General Public License can also be found on
the world wide web at http://www.gnu.org.
FUNCTIONAL DESCRIPTION
INCLUDES
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
+#include <iostream>
+#include <sstream>
+#include <iomanip>
+#include <cstdlib>
+#include "FGFDMExec.h"
#include "FGAerodynamics.h"
-#include "FGPropagate.h"
-#include "FGAircraft.h"
-#include "FGState.h"
-#include "FGMassBalance.h"
-#include <input_output/FGPropertyManager.h>
+#include "input_output/FGPropertyManager.h"
+
+using namespace std;
namespace JSBSim {
-static const char *IdSrc = "$Id$";
+static const char *IdSrc = "$Id: FGAerodynamics.cpp,v 1.45 2012/04/13 13:25:52 jberndt Exp $";
static const char *IdHdr = ID_AERODYNAMICS;
-const unsigned NAxes=6;
-const char* AxisNames[] = { "drag", "side-force", "lift", "rolling-moment",
- "pitching-moment","yawing-moment" };
-
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
CLASS IMPLEMENTATION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
{
Name = "FGAerodynamics";
- AxisIdx["DRAG"] = 0;
- AxisIdx["SIDE"] = 1;
- AxisIdx["LIFT"] = 2;
- AxisIdx["ROLL"] = 3;
- AxisIdx["PITCH"] = 4;
- AxisIdx["YAW"] = 5;
+ AxisIdx["DRAG"] = 0;
+ AxisIdx["SIDE"] = 1;
+ AxisIdx["LIFT"] = 2;
+ AxisIdx["ROLL"] = 3;
+ AxisIdx["PITCH"] = 4;
+ AxisIdx["YAW"] = 5;
+
+ AxisIdx["AXIAL"] = 0;
+ AxisIdx["NORMAL"] = 2;
+
+ AxisIdx["X"] = 0;
+ AxisIdx["Y"] = 1;
+ AxisIdx["Z"] = 2;
- Coeff = new CoeffArray[6];
+ axisType = atNone;
+
+ AeroFunctions = new AeroFunctionArray[6];
impending_stall = stall_hyst = 0.0;
alphaclmin = alphaclmax = 0.0;
clsq = lod = 0.0;
alphaw = 0.0;
bi2vel = ci2vel = 0.0;
+ AeroRPShift = 0;
+ vDeltaRP.InitMatrix();
+
bind();
Debug(0);
{
unsigned int i,j;
- unbind();
-
for (i=0; i<6; i++)
- for (j=0; j<Coeff[i].size(); j++)
- delete Coeff[i][j];
+ for (j=0; j<AeroFunctions[i].size(); j++)
+ delete AeroFunctions[i][j];
- delete[] Coeff;
+ delete[] AeroFunctions;
- for (i=0; i<variables.size(); i++)
- delete variables[i];
+ delete AeroRPShift;
Debug(1);
}
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-bool FGAerodynamics::Run(void)
+bool FGAerodynamics::InitModel(void)
+{
+ if (!FGModel::InitModel()) return false;
+
+ impending_stall = stall_hyst = 0.0;
+ alphaclmin = alphaclmax = 0.0;
+ alphahystmin = alphahystmax = 0.0;
+ clsq = lod = 0.0;
+ alphaw = 0.0;
+ bi2vel = ci2vel = 0.0;
+ AeroRPShift = 0;
+ vDeltaRP.InitMatrix();
+
+ return true;
+}
+//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+bool FGAerodynamics::Run(bool Holding)
{
- unsigned int axis_ctr, ctr, i;
- double alpha, twovel;
- if (FGModel::Run()) return true;
- if (FDMExec->Holding()) return false; // if paused don't execute
+ if (FGModel::Run(Holding)) return true;
+ if (Holding) return false; // if paused don't execute
+
+ unsigned int axis_ctr, ctr;
+ const double twovel=2*in.Vt;
+
+ RunPreFunctions();
// calculate some oft-used quantities for speed
- twovel = 2*Auxiliary->GetVt();
if (twovel != 0) {
- bi2vel = Aircraft->GetWingSpan() / twovel;
- ci2vel = Aircraft->Getcbar() / twovel;
+ bi2vel = in.Wingspan / twovel;
+ ci2vel = in.Wingchord / twovel;
}
- alphaw = Auxiliary->Getalpha() + Aircraft->GetWingIncidence();
- alpha = Auxiliary->Getalpha();
- qbar_area = Aircraft->GetWingArea() * Auxiliary->Getqbar();
+ alphaw = in.Alpha + in.Wingincidence;
+ qbar_area = in.Wingarea * in.Qbar;
if (alphaclmax != 0) {
- if (alpha > 0.85*alphaclmax) {
- impending_stall = 10*(alpha/alphaclmax - 0.85);
+ if (in.Alpha > 0.85*alphaclmax) {
+ impending_stall = 10*(in.Alpha/alphaclmax - 0.85);
} else {
impending_stall = 0;
}
}
if (alphahystmax != 0.0 && alphahystmin != 0.0) {
- if (alpha > alphahystmax) {
+ if (in.Alpha > alphahystmax) {
stall_hyst = 1;
- } else if (alpha < alphahystmin) {
+ } else if (in.Alpha < alphahystmin) {
stall_hyst = 0;
}
}
- vLastFs = vFs;
- vFs.InitMatrix();
-
- // Tell the variable functions to cache their values, so while the aerodynamic
- // functions are being calculated for each axis, these functions do not get
- // calculated each time, but instead use the values that have already
- // been calculated for this frame.
- for (i=0; i<variables.size(); i++) variables[i]->cacheValue(true);
+ vFw.InitMatrix();
+ vFnative.InitMatrix();
for (axis_ctr = 0; axis_ctr < 3; axis_ctr++) {
- for (ctr=0; ctr < Coeff[axis_ctr].size(); ctr++) {
- vFs(axis_ctr+1) += Coeff[axis_ctr][ctr]->GetValue();
+ for (ctr=0; ctr < AeroFunctions[axis_ctr].size(); ctr++) {
+ vFnative(axis_ctr+1) += AeroFunctions[axis_ctr][ctr]->GetValue();
}
}
- // calculate lift coefficient squared
- if ( Auxiliary->Getqbar() > 0) {
- clsq = vFs(eLift) / (Aircraft->GetWingArea()*Auxiliary->Getqbar());
- clsq *= clsq;
+ // Note that we still need to convert to wind axes here, because it is
+ // used in the L/D calculation, and we still may want to look at Lift
+ // and Drag.
+
+ switch (axisType) {
+ case atBodyXYZ: // Forces already in body axes; no manipulation needed
+ vFw = in.Tb2w*vFnative;
+ vForces = vFnative;
+ break;
+ case atLiftDrag: // Copy forces into wind axes
+ vFw = vFnative;
+ vFw(eDrag)*=-1; vFw(eLift)*=-1;
+ vForces = in.Tw2b*vFw;
+ break;
+ case atAxialNormal: // Convert native forces into Axial|Normal|Side system
+ vFw = in.Tb2w*vFnative;
+ vFnative(eX)*=-1; vFnative(eZ)*=-1;
+ vForces = vFnative;
+ break;
+ default:
+ cerr << endl << " A proper axis type has NOT been selected. Check "
+ << "your aerodynamics definition." << endl;
+ exit(-1);
}
- if ( vFs(eDrag) > 0) {
- lod = vFs(eLift) / vFs(eDrag);
+ // Calculate lift coefficient squared
+ if ( in.Qbar > 0) {
+ clsq = vFw(eLift) / (in.Wingarea*in.Qbar);
+ clsq *= clsq;
}
- //correct signs of drag and lift to wind axes convention
- //positive forward, right, down
- vFs(eDrag)*=-1; vFs(eLift)*=-1;
+ // Calculate lift Lift over Drag
+ if ( fabs(vFw(eDrag)) > 0.0) lod = fabs( vFw(eLift) / vFw(eDrag) );
- // transform stability axis forces into body axes
- vForces = State->GetTs2b()*vFs;
+ // Calculate aerodynamic reference point shift, if any. The shift
+ // takes place in the structual axis. That is, if the shift is positive,
+ // it is towards the back (tail) of the vehicle. The AeroRPShift
+ // function should be non-dimensionalized by the wing chord. The
+ // calculated vDeltaRP will be in feet.
+ if (AeroRPShift) vDeltaRP(eX) = AeroRPShift->GetValue()*in.Wingchord;
- vDXYZcg = MassBalance->StructuralToBody(Aircraft->GetXYZrp());
+ vDXYZcg(eX) = in.RPBody(eX) - vDeltaRP(eX); // vDeltaRP is given in the structural frame
+ vDXYZcg(eY) = in.RPBody(eY) + vDeltaRP(eY);
+ vDXYZcg(eZ) = in.RPBody(eZ) - vDeltaRP(eZ);
vMoments = vDXYZcg*vForces; // M = r X F
for (axis_ctr = 0; axis_ctr < 3; axis_ctr++) {
- for (ctr = 0; ctr < Coeff[axis_ctr+3].size(); ctr++) {
- vMoments(axis_ctr+1) += Coeff[axis_ctr+3][ctr]->GetValue();
+ for (ctr = 0; ctr < AeroFunctions[axis_ctr+3].size(); ctr++) {
+ vMoments(axis_ctr+1) += AeroFunctions[axis_ctr+3][ctr]->GetValue();
}
}
+ RunPostFunctions();
+
return false;
}
bool FGAerodynamics::Load(Element *element)
{
string parameter, axis, scratch;
+ string scratch_unit="";
+ string fname="", file="";
Element *temp_element, *axis_element, *function_element;
+ string separator = "/";
+
+ fname = element->GetAttributeValue("file");
+ if (!fname.empty()) {
+ file = FDMExec->GetFullAircraftPath() + separator + fname;
+ document = LoadXMLDocument(file);
+ if (document == 0L) return false;
+ } else {
+ document = element;
+ }
+
+ FGModel::Load(document); // Perform base class Pre-Load
+
+ DetermineAxisSystem(); // Detemine if Lift/Side/Drag, etc. is used.
+
Debug(2);
- if (temp_element = element->FindElement("alphalimits")) {
- alphaclmin = temp_element->FindElementValueAsNumberConvertTo("min", "DEG");
- alphaclmax = temp_element->FindElementValueAsNumberConvertTo("max", "DEG");
+ if ((temp_element = document->FindElement("alphalimits"))) {
+ scratch_unit = temp_element->GetAttributeValue("unit");
+ if (scratch_unit.empty()) scratch_unit = "RAD";
+ alphaclmin = temp_element->FindElementValueAsNumberConvertFromTo("min", scratch_unit, "RAD");
+ alphaclmax = temp_element->FindElementValueAsNumberConvertFromTo("max", scratch_unit, "RAD");
}
- if (temp_element = element->FindElement("hysteresis_limits")) {
- alphahystmin = temp_element->FindElementValueAsNumberConvertTo("min", "DEG");
- alphahystmax = temp_element->FindElementValueAsNumberConvertTo("max", "DEG");
+ if ((temp_element = document->FindElement("hysteresis_limits"))) {
+ scratch_unit = temp_element->GetAttributeValue("unit");
+ if (scratch_unit.empty()) scratch_unit = "RAD";
+ alphahystmin = temp_element->FindElementValueAsNumberConvertFromTo("min", scratch_unit, "RAD");
+ alphahystmax = temp_element->FindElementValueAsNumberConvertFromTo("max", scratch_unit, "RAD");
}
- function_element = element->FindElement("function");
- while (function_element) {
- variables.push_back( new FGFunction(PropertyManager, function_element) );
- function_element = element->FindNextElement("function");
+ if ((temp_element = document->FindElement("aero_ref_pt_shift_x"))) {
+ function_element = temp_element->FindElement("function");
+ AeroRPShift = new FGFunction(PropertyManager, function_element);
}
- axis_element = element->FindElement("axis");
+ axis_element = document->FindElement("axis");
while (axis_element) {
- CoeffArray ca;
+ AeroFunctionArray ca;
axis = axis_element->GetAttributeValue("name");
function_element = axis_element->FindElement("function");
while (function_element) {
- ca.push_back( new FGFunction(PropertyManager, function_element) );
+ string current_func_name = function_element->GetAttributeValue("name");
+ try {
+ ca.push_back( new FGFunction(PropertyManager, function_element) );
+ } catch (string const str) {
+ cerr << endl << fgred << "Error loading aerodynamic function in "
+ << current_func_name << ":" << str << " Aborting." << reset << endl;
+ return false;
+ }
function_element = axis_element->FindNextElement("function");
}
- Coeff[AxisIdx[axis]] = ca;
- axis_element = element->FindNextElement("axis");
+ AeroFunctions[AxisIdx[axis]] = ca;
+ axis_element = document->FindNextElement("axis");
}
+ PostLoad(document, PropertyManager); // Perform base class Post-Load
+
return true;
}
+//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+//
+// This private class function checks to verify consistency in the choice of
+// aerodynamic axes used in the config file. One set of LIFT|DRAG|SIDE, or
+// X|Y|Z, or AXIAL|NORMAL|SIDE must be chosen; mixed system axes are not allowed.
+// Note that if the "SIDE" axis specifier is entered first in a config file,
+// a warning message will be given IF the AXIAL|NORMAL specifiers are also given.
+// This is OK, and the warning is due to the SIDE specifier used for both
+// the Lift/Drag and Axial/Normal axis systems.
+
+void FGAerodynamics::DetermineAxisSystem()
+{
+ Element* axis_element = document->FindElement("axis");
+ string axis;
+ while (axis_element) {
+ axis = axis_element->GetAttributeValue("name");
+ if (axis == "LIFT" || axis == "DRAG") {
+ if (axisType == atNone) axisType = atLiftDrag;
+ else if (axisType != atLiftDrag) {
+ cerr << endl << " Mixed aerodynamic axis systems have been used in the"
+ << " aircraft config file. (LIFT DRAG)" << endl;
+ }
+ } else if (axis == "SIDE") {
+ if (axisType != atNone && axisType != atLiftDrag && axisType != atAxialNormal) {
+ cerr << endl << " Mixed aerodynamic axis systems have been used in the"
+ << " aircraft config file. (SIDE)" << endl;
+ }
+ } else if (axis == "AXIAL" || axis == "NORMAL") {
+ if (axisType == atNone) axisType = atAxialNormal;
+ else if (axisType != atAxialNormal) {
+ cerr << endl << " Mixed aerodynamic axis systems have been used in the"
+ << " aircraft config file. (NORMAL AXIAL)" << endl;
+ }
+ } else if (axis == "X" || axis == "Y" || axis == "Z") {
+ if (axisType == atNone) axisType = atBodyXYZ;
+ else if (axisType != atBodyXYZ) {
+ cerr << endl << " Mixed aerodynamic axis systems have been used in the"
+ << " aircraft config file. (XYZ)" << endl;
+ }
+ } else if (axis != "ROLL" && axis != "PITCH" && axis != "YAW") { // error
+ cerr << endl << " An unknown axis type, " << axis << " has been specified"
+ << " in the aircraft configuration file." << endl;
+ exit(-1);
+ }
+ axis_element = document->FindNextElement("axis");
+ }
+ if (axisType == atNone) {
+ axisType = atLiftDrag;
+ cerr << endl << " The aerodynamic axis system has been set by default"
+ << " to the Lift/Side/Drag system." << endl;
+ }
+}
+
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-string FGAerodynamics::GetCoefficientStrings(string delimeter)
+string FGAerodynamics::GetAeroFunctionStrings(const string& delimeter) const
{
- string CoeffStrings = "";
+ string AeroFunctionStrings = "";
bool firstime = true;
unsigned int axis, sd;
- for (sd = 0; sd < variables.size(); sd++) {
- if (firstime) {
- firstime = false;
- } else {
- CoeffStrings += delimeter;
+ for (axis = 0; axis < 6; axis++) {
+ for (sd = 0; sd < AeroFunctions[axis].size(); sd++) {
+ if (firstime) {
+ firstime = false;
+ } else {
+ AeroFunctionStrings += delimeter;
+ }
+ AeroFunctionStrings += AeroFunctions[axis][sd]->GetName();
}
- CoeffStrings += variables[sd]->GetName();
}
- for (axis = 0; axis < 6; axis++) {
- for (sd = 0; sd < Coeff[axis].size(); sd++) {
- CoeffStrings += delimeter;
- CoeffStrings += Coeff[axis][sd]->GetName();
+ string FunctionStrings = FGModelFunctions::GetFunctionStrings(delimeter);
+
+ if (FunctionStrings.size() > 0) {
+ if (AeroFunctionStrings.size() > 0) {
+ AeroFunctionStrings += delimeter + FunctionStrings;
+ } else {
+ AeroFunctionStrings = FunctionStrings;
}
}
- return CoeffStrings;
+
+ return AeroFunctionStrings;
}
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-string FGAerodynamics::GetCoefficientValues(string delimeter)
+string FGAerodynamics::GetAeroFunctionValues(const string& delimeter) const
{
- string SDValues = "";
- bool firstime = true;
- unsigned int sd;
+ ostringstream buf;
- for (sd = 0; sd < variables.size(); sd++) {
- if (firstime) {
- firstime = false;
- } else {
- SDValues += delimeter;
+ for (unsigned int axis = 0; axis < 6; axis++) {
+ for (unsigned int sd = 0; sd < AeroFunctions[axis].size(); sd++) {
+ if (buf.tellp() > 0) buf << delimeter;
+ buf << AeroFunctions[axis][sd]->GetValue();
}
- SDValues += variables[sd]->GetValueAsString();
}
- for (unsigned int axis = 0; axis < 6; axis++) {
- for (unsigned int sd = 0; sd < Coeff[axis].size(); sd++) {
- SDValues += delimeter;
- SDValues += Coeff[axis][sd]->GetValueAsString();
+ string FunctionValues = FGModelFunctions::GetFunctionValues(delimeter);
+
+ if (FunctionValues.size() > 0) {
+ if (buf.str().size() > 0) {
+ buf << delimeter << FunctionValues;
+ } else {
+ buf << FunctionValues;
}
}
- return SDValues;
+ return buf.str();
}
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
PropertyManager->Tie("moments/n-aero-lbsft", this,3,
(PMF)&FGAerodynamics::GetMoments);
PropertyManager->Tie("forces/fwx-aero-lbs", this,1,
- (PMF)&FGAerodynamics::GetvFs);
+ (PMF)&FGAerodynamics::GetvFw);
PropertyManager->Tie("forces/fwy-aero-lbs", this,2,
- (PMF)&FGAerodynamics::GetvFs);
+ (PMF)&FGAerodynamics::GetvFw);
PropertyManager->Tie("forces/fwz-aero-lbs", this,3,
- (PMF)&FGAerodynamics::GetvFs);
+ (PMF)&FGAerodynamics::GetvFw);
PropertyManager->Tie("forces/lod-norm", this,
&FGAerodynamics::GetLoD);
PropertyManager->Tie("aero/cl-squared", this,
&FGAerodynamics::GetClSquared);
PropertyManager->Tie("aero/qbar-area", &qbar_area);
- PropertyManager->Tie("aero/alpha-max-deg", this,
+ PropertyManager->Tie("aero/alpha-max-rad", this,
&FGAerodynamics::GetAlphaCLMax,
&FGAerodynamics::SetAlphaCLMax,
true);
- PropertyManager->Tie("aero/alpha-min-deg", this,
+ PropertyManager->Tie("aero/alpha-min-rad", this,
&FGAerodynamics::GetAlphaCLMin,
&FGAerodynamics::SetAlphaCLMin,
true);
&FGAerodynamics::GetHysteresisParm);
}
-//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-void FGAerodynamics::unbind(void)
-{
- unsigned i,j;
-
- PropertyManager->Untie("forces/fbx-aero-lbs");
- PropertyManager->Untie("forces/fby-aero-lbs");
- PropertyManager->Untie("forces/fbz-aero-lbs");
- PropertyManager->Untie("moments/l-aero-lbsft");
- PropertyManager->Untie("moments/m-aero-lbsft");
- PropertyManager->Untie("moments/n-aero-lbsft");
- PropertyManager->Untie("forces/fwx-aero-lbs");
- PropertyManager->Untie("forces/fwy-aero-lbs");
- PropertyManager->Untie("forces/fwz-aero-lbs");
- PropertyManager->Untie("forces/lod-norm");
- PropertyManager->Untie("aero/cl-squared");
- PropertyManager->Untie("aero/qbar-area");
- PropertyManager->Untie("aero/alpha-max-deg");
- PropertyManager->Untie("aero/alpha-min-deg");
- PropertyManager->Untie("aero/bi2vel");
- PropertyManager->Untie("aero/ci2vel");
- PropertyManager->Untie("aero/alpha-wing-rad");
- PropertyManager->Untie("aero/stall-hyst-norm");
- PropertyManager->Untie("systems/stall-warn-norm");
-
-}
-
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// The bitmasked value choices are as follows:
// unset: In this case (the default) JSBSim would only print
if (debug_lvl & 1) { // Standard console startup message output
if (from == 2) { // Loader
- cout << endl << " Aerodynamics: " << endl;
+ switch (axisType) {
+ case (atLiftDrag):
+ cout << endl << " Aerodynamics (Lift|Side|Drag axes):" << endl << endl;
+ break;
+ case (atAxialNormal):
+ cout << endl << " Aerodynamics (Axial|Side|Normal axes):" << endl << endl;
+ break;
+ case (atBodyXYZ):
+ cout << endl << " Aerodynamics (X|Y|Z axes):" << endl << endl;
+ break;
+ case (atNone):
+ cout << endl << " Aerodynamics (undefined axes):" << endl << endl;
+ break;
+ }
}
}
if (debug_lvl & 2 ) { // Instantiation/Destruction notification