GammaLib 2.0.0
Loading...
Searching...
No Matches
GModelSpatialEllipticalDisk.cpp
Go to the documentation of this file.
1/***************************************************************************
2 * GModelSpatialEllipticalDisk.cpp - Elliptical disk source model class *
3 * ----------------------------------------------------------------------- *
4 * copyright (C) 2013-2022 by Michael Mayer *
5 * ----------------------------------------------------------------------- *
6 * *
7 * This program is free software: you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation, either version 3 of the License, or *
10 * (at your option) any later version. *
11 * *
12 * This program is distributed in the hope that it will be useful, *
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 * GNU General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU General Public License *
18 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
19 * *
20 ***************************************************************************/
21/**
22 * @file GModelSpatialEllipticalDisk.cpp
23 * @brief Elliptical disk model class implementation
24 * @author Michael Mayer
25 */
26
27/* __ Includes ___________________________________________________________ */
28#ifdef HAVE_CONFIG_H
29#include <config.h>
30#endif
31#include "GException.hpp"
32#include "GTools.hpp"
33#include "GMath.hpp"
37
38/* __ Constants __________________________________________________________ */
39
40/* __ Globals ____________________________________________________________ */
42const GModelSpatialRegistry g_elliptical_disk_registry(&g_elliptical_disk_seed);
43
44/* __ Method name definitions ____________________________________________ */
45#define G_CONSTRUCTOR "GModelSpatialEllipticalDisk::"\
46 "GModelSpatialEllipticalDisk(GSkyDir&, double&, double&, "\
47 "double&, std::string&)"
48#define G_READ "GModelSpatialEllipticalDisk::read(GXmlElement&)"
49#define G_WRITE "GModelSpatialEllipticalDisk::write(GXmlElement&)"
50
51/* __ Macros _____________________________________________________________ */
52
53/* __ Coding definitions _________________________________________________ */
54
55/* __ Debug definitions __________________________________________________ */
56
57
58/*==========================================================================
59 = =
60 = Constructors/destructors =
61 = =
62 ==========================================================================*/
63
64/***********************************************************************//**
65 * @brief Void constructor
66 ***************************************************************************/
69{
70 // Initialise members
72
73 // Return
74 return;
75}
76
77
78/***********************************************************************//**
79 * @brief Disk constructor
80 *
81 * @param[in] dir Sky position of disk centre.
82 * @param[in] semimajor Semi-major axis (degrees).
83 * @param[in] semiminor Semi-minor axis (degrees).
84 * @param[in] posangle Position angle of semi-major axis (degrees).
85 * @param[in] coordsys Coordinate system (either "CEL" or "GAL")
86 *
87 * @exception GException::invalid_argument
88 * Invalid @p coordsys argument specified.
89 *
90 * Construct elliptical disk model from sky position of the ellipse centre
91 * (@p dir), the @p semimajor and @p semiminor axes, and the position
92 * angle (@p posangle). The @p coordsys parameter specifies whether the sky
93 * direction should be interpreted in the celestial or Galactic coordinate
94 * system.
95 ***************************************************************************/
97 const double& semimajor,
98 const double& semiminor,
99 const double& posangle,
100 const std::string& coordsys) :
102{
103 // Throw an exception if the coordinate system is invalid
104 if ((coordsys != "CEL") && (coordsys != "GAL")) {
105 std::string msg = "Invalid coordinate system \""+coordsys+"\" "
106 "specified. Please specify either \"CEL\" or "
107 "\"GAL\".";
109 }
110
111 // Initialise members
112 init_members();
113
114 // Set parameter names
115 if (coordsys == "CEL") {
116 m_lon.name("RA");
117 m_lat.name("DEC");
118 }
119 else {
120 m_lon.name("GLON");
121 m_lat.name("GLAT");
122 }
123
124 // Assign parameters
125 this->dir(dir);
126 this->semiminor(semiminor);
127 this->semimajor(semimajor);
128 this->posangle(posangle);
129
130 // Return
131 return;
132}
133
134
135/***********************************************************************//**
136 * @brief XML constructor
137 *
138 * @param[in] xml XML element.
139 *
140 * Constructs elliptical disk model by extracting information from an XML
141 * element. See the read() method for more information about the expected
142 * structure of the XML element.
143 ***************************************************************************/
146{
147 // Initialise members
148 init_members();
149
150 // Read information from XML element
151 read(xml);
152
153 // Return
154 return;
155}
156
157
158/***********************************************************************//**
159 * @brief Copy constructor
160 *
161 * @param[in] model Elliptical disk model.
162 ***************************************************************************/
165{
166 // Initialise members
167 init_members();
168
169 // Copy members
170 copy_members(model);
171
172 // Return
173 return;
174}
175
176
177/***********************************************************************//**
178 * @brief Destructor
179 ***************************************************************************/
181{
182 // Free members
183 free_members();
184
185 // Return
186 return;
187}
188
189
190/*==========================================================================
191 = =
192 = Operators =
193 = =
194 ==========================================================================*/
195
196/***********************************************************************//**
197 * @brief Assignment operator
198 *
199 * @param[in] model Elliptical disk model.
200 * @return Elliptical disk model.
201 ***************************************************************************/
203{
204 // Execute only if object is not identical
205 if (this != &model) {
206
207 // Copy base class members
209
210 // Free members
211 free_members();
212
213 // Initialise members
214 init_members();
215
216 // Copy members
217 copy_members(model);
218
219 } // endif: object was not identical
220
221 // Return
222 return *this;
223}
224
225
226/*==========================================================================
227 = =
228 = Public methods =
229 = =
230 ==========================================================================*/
231
232/***********************************************************************//**
233 * @brief Clear elliptical disk model
234 ***************************************************************************/
236{
237 // Free class members (base and derived classes, derived class first)
238 free_members();
241
242 // Initialise members
245 init_members();
246
247 // Return
248 return;
249}
250
251
252/***********************************************************************//**
253 * @brief Clone elliptical disk model
254 *
255 * @return Pointer to deep copy of elliptical disk model.
256 ***************************************************************************/
258{
259 // Clone elliptical disk model
260 return new GModelSpatialEllipticalDisk(*this);
261}
262
263
264/***********************************************************************//**
265 * @brief Evaluate function (in units of sr^-1)
266 *
267 * @param[in] theta Angular distance from disk centre (radians).
268 * @param[in] posangle Position angle (counterclockwise from North) (radians).
269 * @param[in] energy Photon energy.
270 * @param[in] time Photon arrival time.
271 * @param[in] gradients Compute gradients?
272 * @return Model value.
273 *
274 * Evaluates the spatial component for an elliptical disk source model. The
275 * disk source model is an elliptical function
276 * \f$S_{\rm p}(\theta, \phi | E, t)\f$, where
277 * \f$\theta\f$ is the angular separation between elliptical disk centre and
278 * the actual location and \f$\phi\f$ the position angle with respect to the
279 * ellipse centre, counted counterclockwise from North.
280 *
281 * The function \f$f(\theta, \phi)\f$ is given by
282 *
283 * \f[
284 * S_{\rm p}(\theta, \phi | E, t) = \left \{
285 * \begin{array}{l l}
286 * {\tt m\_norm}
287 * & \mbox{if} \, \, \theta \le \theta_0 \\
288 * \\
289 * 0 & \mbox{else}
290 * \end{array}
291 * \right .
292 * \f]
293 *
294 * where \f$\theta_0\f$ is the effective radius of the ellipse on the sphere
295 * given by
296 *
297 * \f[\theta_0\ =
298 * \frac{ab}{\sqrt{b^2 \cos^2(\phi-\phi_0) + a^2 \sin^2(\phi-\phi_0)}}\f]
299 *
300 * and
301 * \f$a\f$ is the semi-major axis of the ellipse,
302 * \f$b\f$ is the semi-minor axis, and
303 * \f$\phi_0\f$ is the position angle of the ellipse, counted
304 * counterclockwise from North.
305 *
306 * The normalisation constant \f${\tt m\_norm}\f$ which is the inverse of the
307 * solid angle subtended by an ellipse is given by
308 *
309 * The method will not compute analytical parameter gradients, even if the
310 * @p gradients argument is set to true. Radial disk parameter gradients
311 * need to be computed numerically.
312 *
313 * @todo Quote formula for ellipse solid angle
314 *
315 * (see the update() method).
316 ***************************************************************************/
317double GModelSpatialEllipticalDisk::eval(const double& theta,
318 const double& posangle,
319 const GEnergy& energy,
320 const GTime& time,
321 const bool& gradients) const
322{
323 // Initialise value
324 double value = 0.0;
325
326 // Continue only if we're inside circle enclosing the ellipse
327 if (theta <= theta_max()) {
328
329 // Update precomputation cache
330 update();
331
332 // Perform computations
333 double diff_angle = posangle - m_posangle.value() * gammalib::deg2rad;
334 double cosinus = std::cos(diff_angle);
335 double sinus = std::sin(diff_angle);
336 double arg1 = m_semiminor_rad * cosinus;
337 double arg2 = m_semimajor_rad * sinus;
338 double r_ell = m_semiminor_rad * m_semimajor_rad /
339 std::sqrt(arg1*arg1 + arg2*arg2);
340
341 // Set value
342 value = (theta <= r_ell) ? m_norm : 0.0;
343
344 // Compile option: Check for NaN/Inf
345 #if defined(G_NAN_CHECK)
346 if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
347 std::cout << "*** ERROR: GModelSpatialEllipticalDisk::eval";
348 std::cout << "(theta=" << theta << "): NaN/Inf encountered";
349 std::cout << "(posangle=" << posangle << "): NaN/Inf encountered";
350 std::cout << " (value=" << value;
351 std::cout << ", R_ellipse=" << r_ell;
352 std::cout << ", diff_angle=" << diff_angle;
353 std::cout << ", m_norm=" << m_norm;
354 std::cout << ")" << std::endl;
355 }
356 #endif
357
358 } // endif: position was inside enclosing circle
359
360 // Return value
361 return value;
362}
363
364
365/***********************************************************************//**
366 * @brief Returns MC sky direction
367 *
368 * @param[in] energy Photon energy.
369 * @param[in] time Photon arrival time.
370 * @param[in,out] ran Random number generator.
371 * @return Sky direction.
372 *
373 * Draws an arbitrary sky position from the 2D disk distribution.
374 *
375 * @todo Test function
376 ***************************************************************************/
378 const GTime& time,
379 GRan& ran) const
380{
381 // Update precomputation cache
382 update();
383
384 // Initialise photon
385 GPhoton photon;
386 photon.energy(energy);
387 photon.time(time);
388
389 // Draw randomly from the radial disk
390 // and reject the value if its outside the ellipse
391 do {
392
393 // Simulate offset from photon arrival direction
394 double cosrad = std::cos(semimajor() * gammalib::deg2rad);
395 double theta = std::acos(1.0 - ran.uniform() * (1.0 - cosrad)) *
397 double phi = 360.0 * ran.uniform();
398
399 // Rotate sky direction by offset
400 GSkyDir sky_dir = dir();
401 sky_dir.rotate_deg(phi, theta);
402
403 // Set photon sky direction
404 photon.dir(sky_dir);
405
406 } while(GModelSpatialElliptical::eval(photon) <= 0.0);
407
408 // Return photon direction
409 return (photon.dir());
410
411}
412
413
414/***********************************************************************//**
415 * @brief Checks where model contains specified sky direction
416 *
417 * @param[in] dir Sky direction.
418 * @param[in] margin Margin to be added to sky direction (degrees)
419 * @return True if the model contains the sky direction.
420 *
421 * Signals whether a sky direction is contained in the elliptical disk
422 * model.
423 *
424 * @todo Implement correct evaluation of effective ellipse radius.
425 ***************************************************************************/
427 const double& margin) const
428{
429 // Compute distance to centre (radian)
430 double distance = dir.dist(this->dir());
431
432 // Return flag
433 return (distance <= theta_max() + margin*gammalib::deg2rad);
434}
435
436
437/***********************************************************************//**
438 * @brief Return maximum model radius (in radians)
439 *
440 * @return Returns maximum model radius.
441 ***************************************************************************/
443{
444 // Set maximum model radius
445 double theta_max = (semimajor() > semiminor())
448
449 // Return value
450 return theta_max;
451}
452
453
454/***********************************************************************//**
455 * @brief Read model from XML element
456 *
457 * @param[in] xml XML element.
458 *
459 * @exception GException::model_invalid_parnum
460 * Invalid number of model parameters found in XML element.
461 * @exception GException::model_invalid_parnames
462 * Invalid model parameter names found in XML element.
463 *
464 * Reads the elliptical disk model information from an XML element. The XML
465 * element shall have either the format
466 *
467 * <spatialModel type="EllipticalDisk">
468 * <parameter name="RA" scale="1.0" value="83.6331" min="-360" max="360" free="1"/>
469 * <parameter name="DEC" scale="1.0" value="22.0145" min="-90" max="90" free="1"/>
470 * <parameter name="PA" scale="1.0" value="45.0" min="-360" max="360" free="1"/>
471 * <parameter name="MinorRadius" scale="1.0" value="0.5" min="0.001" max="10" free="1"/>
472 * <parameter name="MajorRadius" scale="1.0" value="2.0" min="0.001" max="10" free="1"/>
473 * </spatialModel>
474 *
475 * or
476 *
477 * <spatialModel type="EllipticalDisk">
478 * <parameter name="GLON" scale="1.0" value="83.6331" min="-360" max="360" free="1"/>
479 * <parameter name="GLAT" scale="1.0" value="22.0145" min="-90" max="90" free="1"/>
480 * <parameter name="PA" scale="1.0" value="45.0" min="-360" max="360" free="1"/>
481 * <parameter name="MinorRadius" scale="1.0" value="0.5" min="0.001" max="10" free="1"/>
482 * <parameter name="MajorRadius" scale="1.0" value="2.0" min="0.001" max="10" free="1"/>
483 * </spatialModel>
484 *
485 * @todo Implement a test of the ellipse boundary. The axes
486 * and axes minimum should be >0.
487 ***************************************************************************/
489{
490 // Verify number of model parameters
492
493 // Read disk location
495
496 // Get parameters
499
500 // Read parameters
501 m_semiminor.read(*minor);
502 m_semimajor.read(*major);
503
504 // Return
505 return;
506}
507
508
509/***********************************************************************//**
510 * @brief Write model into XML element
511 *
512 * @param[in] xml XML element into which model information is written.
513 *
514 * Write the elliptical disk model information into an XML element. The XML
515 * element will have the format
516 *
517 * <spatialModel type="EllipticalDisk">
518 * <parameter name="RA" scale="1.0" value="83.6331" min="-360" max="360" free="1"/>
519 * <parameter name="DEC" scale="1.0" value="22.0145" min="-90" max="90" free="1"/>
520 * <parameter name="PA" scale="1.0" value="45.0" min="-360" max="360" free="1"/>
521 * <parameter name="MinorRadius" scale="1.0" value="0.5" min="0.001" max="10" free="1"/>
522 * <parameter name="MajorRadius" scale="1.0" value="2.0" min="0.001" max="10" free="1"/>
523 * </spatialModel>
524 *
525 ***************************************************************************/
527{
528 // Verify model type
530
531 // Write disk location
533
534 // Get or create parameters
537
538 // Write parameters
539 m_semiminor.write(*minor);
540 m_semimajor.write(*major);
541
542 // Return
543 return;
544}
545
546
547/***********************************************************************//**
548 * @brief Print information
549 *
550 * @param[in] chatter Chattiness.
551 * @return String containing model information.
552 ***************************************************************************/
553std::string GModelSpatialEllipticalDisk::print(const GChatter& chatter) const
554{
555 // Initialise result string
556 std::string result;
557
558 // Continue only if chatter is not silent
559 if (chatter != SILENT) {
560
561 // Append header
562 result.append("=== GModelSpatialEllipticalDisk ===");
563
564 // Append parameters
565 result.append("\n"+gammalib::parformat("Number of parameters"));
566 result.append(gammalib::str(size()));
567 for (int i = 0; i < size(); ++i) {
568 result.append("\n"+m_pars[i]->print(chatter));
569 }
570
571 } // endif: chatter was not silent
572
573 // Return result
574 return result;
575}
576
577
578/*==========================================================================
579 = =
580 = Private methods =
581 = =
582 ==========================================================================*/
583
584/***********************************************************************//**
585 * @brief Initialise class members
586 ***************************************************************************/
588{
589 // Initialise model type
590 m_type = "EllipticalDisk";
591
592 // Initialise precomputation cache. Note that zero values flag
593 // uninitialised as a zero radius is not meaningful
594 m_last_semiminor = 0.0;
595 m_last_semimajor = 0.0;
596 m_semiminor_rad = 0.0;
597 m_semimajor_rad = 0.0;
598 m_norm = 0.0;
599
600 // Return
601 return;
602}
603
604
605/***********************************************************************//**
606 * @brief Copy class members
607 *
608 * @param[in] model Elliptical disk model.
609 *
610 * We do not have to push back the members on the parameter stack as this
611 * should have been done by init_members() that was called before. Otherwise
612 * we would have the radius twice on the stack.
613 ***************************************************************************/
615{
616 // Copy members
617 m_type = model.m_type; // Needed to conserve model type
618
619 // Copy precomputation cache
624 m_norm = model.m_norm;
625
626 // Return
627 return;
628}
629
630
631/***********************************************************************//**
632 * @brief Delete class members
633 ***************************************************************************/
635{
636 // Return
637 return;
638}
639
640
641/***********************************************************************//**
642 * @brief Update precomputation cache
643 *
644 * Computes the normalization
645 * \f[
646 * {\tt m\_norm} = \frac{1}{2 \pi (1 - \cos a) (1 - \cos b)}
647 * \f]
648 *
649 * @todo check this formula
650 ***************************************************************************/
652{
653 // Update if one axis has changed
655
656 // Store last values
659
660 // Compute axes in radians
663
664 // Perform precomputations
665 double denom = gammalib::twopi *
666 std::sqrt((1.0 - std::cos(m_semiminor_rad)) *
667 (1.0 - std::cos(m_semimajor_rad)));
668 m_norm = (denom > 0.0) ? 1.0 / denom : 0.0;
669
670 } // endif: update required
671
672 // Return
673 return;
674}
675
676
677/***********************************************************************//**
678 * @brief Set boundary sky region
679 ***************************************************************************/
681{
682 // Set maximum model radius
683 double max_radius = (semimajor() > semiminor()) ? semimajor() : semiminor();
684
685 // Set sky region circle
686 GSkyRegionCircle region(dir(), max_radius);
687
688 // Set region (circumvent const correctness)
689 const_cast<GModelSpatialEllipticalDisk*>(this)->m_region = region;
690
691 // Return
692 return;
693}
#define G_WRITE
#define G_READ
Exception handler interface definition.
Mathematical function definitions.
#define G_CONSTRUCTOR
Definition GMatrix.cpp:41
const GModelSpatialEllipticalDisk g_elliptical_disk_seed
Elliptical disk model class interface definition.
Radial disk model class interface definition.
Spatial model registry class definition.
Gammalib tools definition.
GChatter
Definition GTypemaps.hpp:33
@ SILENT
Definition GTypemaps.hpp:34
Class that handles energies in a unit independent way.
Definition GEnergy.hpp:48
void write(GXmlElement &xml) const
Set or update parameter attributes in XML element.
void read(const GXmlElement &xml)
Extract parameter attributes from XML element.
virtual void set_region(void) const
Set boundary sky region.
virtual GModelSpatialEllipticalDisk * clone(void) const
Clone elliptical disk model.
virtual void write(GXmlElement &xml) const
Write model into XML element.
virtual bool contains(const GSkyDir &dir, const double &margin=0.0) const
Checks where model contains specified sky direction.
double m_last_semiminor
Last semi-minor axis.
void init_members(void)
Initialise class members.
virtual std::string print(const GChatter &chatter=NORMAL) const
Print information.
virtual GSkyDir mc(const GEnergy &energy, const GTime &time, GRan &ran) const
Returns MC sky direction.
void copy_members(const GModelSpatialEllipticalDisk &model)
Copy class members.
virtual GModelSpatialEllipticalDisk & operator=(const GModelSpatialEllipticalDisk &model)
Assignment operator.
virtual double eval(const double &theta, const double &posangle, const GEnergy &energy, const GTime &time, const bool &gradients=false) const
Evaluate function (in units of sr^-1)
virtual ~GModelSpatialEllipticalDisk(void)
Destructor.
virtual void clear(void)
Clear elliptical disk model.
virtual double theta_max(void) const
Return maximum model radius (in radians)
void update(void) const
Update precomputation cache.
void free_members(void)
Delete class members.
double m_last_semimajor
Last semi-major axis.
GModelSpatialEllipticalDisk(void)
Void constructor.
virtual void read(const GXmlElement &xml)
Read model from XML element.
Abstract elliptical spatial model base class.
void init_members(void)
Initialise class members.
GModelPar m_semiminor
Semi-minor axis of ellipse (deg)
double semimajor(void) const
Return semi-major axis of ellipse.
virtual void write(GXmlElement &xml) const
Write model into XML element.
GModelPar m_semimajor
Semi-major axis of ellipse (deg)
GModelPar m_lat
Declination or Galactic latitude (deg)
virtual double eval(const double &theta, const double &posangle, const GEnergy &energy, const GTime &time, const bool &gradients=false) const =0
void free_members(void)
Delete class members.
double posangle(void) const
Return Position Angle of model.
virtual GModelSpatialElliptical & operator=(const GModelSpatialElliptical &model)
Assignment operator.
GModelPar m_lon
Right Ascension or Galactic longitude (deg)
const GSkyDir & dir(void) const
Return position of elliptical spatial model.
std::string coordsys(void) const
Return coordinate system.
double semiminor(void) const
Return semi-minor axis of ellipse.
virtual void read(const GXmlElement &xml)
Read model from XML element.
GModelPar m_posangle
Position angle from North, counterclockwise (deg)
Interface definition for the spatial model registry class.
std::string m_type
Spatial model type.
std::string type(void) const
Return model type.
const GSkyRegion * region(void) const
Return boundary sky region.
std::vector< GModelPar * > m_pars
Parameter pointers.
void init_members(void)
Initialise class members.
int size(void) const
Return number of parameters.
void free_members(void)
Delete class members.
GSkyRegionCircle m_region
Bounding circle.
double value(void) const
Return parameter value.
const std::string & name(void) const
Return parameter name.
Class that handles photons.
Definition GPhoton.hpp:47
const GTime & time(void) const
Return photon time.
Definition GPhoton.hpp:134
const GSkyDir & dir(void) const
Return photon sky direction.
Definition GPhoton.hpp:110
const GEnergy & energy(void) const
Return photon energy.
Definition GPhoton.hpp:122
Random number generator class.
Definition GRan.hpp:44
double uniform(void)
Returns random double precision floating value in range 0 to 1.
Definition GRan.cpp:242
Sky direction class.
Definition GSkyDir.hpp:62
void rotate_deg(const double &phi, const double &theta)
Rotate sky direction by zenith and azimuth angle.
Definition GSkyDir.cpp:424
double dist(const GSkyDir &dir) const
Compute angular distance between sky directions in radians.
Definition GSkyDir.hpp:271
Interface for the circular sky region class.
Time class.
Definition GTime.hpp:55
XML element node class.
std::string parformat(const std::string &s, const int &indent=0)
Convert string in parameter format.
Definition GTools.cpp:1143
bool is_infinite(const double &x)
Signal if argument is infinite.
Definition GTools.hpp:184
bool is_notanumber(const double &x)
Signal if argument is not a number.
Definition GTools.hpp:201
std::string str(const unsigned short int &value)
Convert unsigned short integer value into string.
Definition GTools.cpp:489
const GXmlElement * xml_get_par(const std::string &origin, const GXmlElement &xml, const std::string &name)
Return pointer to parameter with given name in XML element.
Definition GTools.cpp:1689
GXmlElement * xml_need_par(const std::string &origin, GXmlElement &xml, const std::string &name)
Return pointer to parameter with given name in XML element.
Definition GTools.cpp:1637
const double deg2rad
Definition GMath.hpp:43
void xml_check_parnum(const std::string &origin, const GXmlElement &xml, const int &number)
Checks number of parameters.
Definition GTools.cpp:1777
const double twopi
Definition GMath.hpp:36
const double rad2deg
Definition GMath.hpp:44
void xml_check_type(const std::string &origin, GXmlElement &xml, const std::string &type)
Checks the model type.
Definition GTools.cpp:1819