text:
PKG_SCROOGE=(
  "pantsbuild.pants.contrib.scrooge"
  "//contrib/scrooge/src/python/pants/contrib/scrooge:plugin"
  "pkg_scrooge_install_test"
)
function pkg_scrooge_install_test() {
  execute_packaged_pants_with_internal_backends \
    --plugins="['pantsbuild.pants.contrib.scrooge==$(local_version)']" \
    --explain gen | grep "scrooge" &> /dev/null && \
  execute_packaged_pants_with_internal_backends \
    --plugins="['pantsbuild.pants.contrib.scrooge==$(local_version)']" \
    goals | grep "thrift-linter" &> /dev/null
}

PKG_BUILDGEN=(
  "pantsbuild.pants.contrib.buildgen"
  "//contrib/buildgen/src/python/pants/contrib/buildgen:plugin"
  "pkg_buildgen_install_test"
)
function pkg_buildgen_install_test() {
  PIP_ARGS="$@"
  pip install ${PIP_ARGS} pantsbuild.pants.contrib.buildgen==$(local_version) && \
  python -c "from pants.contrib.buildgen.build_file_manipulator import *"
}

PKG_SPINDLE=(
  "pantsbuild.pants.contrib.spindle"
  "//contrib/spindle/src/python/pants/contrib/spindle:plugin"
  "pkg_spindle_install_test"
)
function pkg_spindle_install_test() {
  execute_packaged_pants_with_internal_backends \
    --plugins="['pantsbuild.pants.contrib.spindle==$(local_version)']" \
    --explain gen | grep "spindle" &> /dev/null
}

PKG_GO=(
  "pantsbuild.pants.contrib.go"
  "//contrib/go/src/python/pants/contrib/go:plugin"
  "pkg_go_install_test"
)
function pkg_go_install_test() {
  execute_packaged_pants_with_internal_backends \
    "extra_bootstrap_buildfiles='${ROOT}/contrib/go/BUILD'" \
      --plugins="['pantsbuild.pants.contrib.go==$(local_version)']" \
      test.go contrib/go/examples::
}

PKG_NODE=(
  "pantsbuild.pants.contrib.node"
  "//contrib/node/src/python/pants/contrib/node:plugin"
  "pkg_node_install_test"
)
function pkg_node_install_test() {
  (cat << EOF
var typ = require('typ');
console.log("type of boolean is: " + typ.BOOLEAN);
EOF
  ) | \
  execute_packaged_pants_with_internal_backends \
    "extra_bootstrap_buildfiles='${ROOT}/contrib/node/BUILD'" \
      --plugins="['pantsbuild.pants.contrib.node==$(local_version)']" \
      repl.node contrib/node/examples::
}

# Once individual (new) package is declared above, insert it into the array below)
CONTRIB_PACKAGES=(
  PKG_SCROOGE
  PKG_BUILDGEN
  PKG_SPINDLE
  PKG_GO
  PKG_NODE
)


text:
//-------------------------------------------------------------------------------
// Annotations
//-------------------------------------------------------------------------------

//@Export('bugfs.Tarball')

//@Require('Class')
//@Require('Flows')
//@Require('TypeUtil')
//@Require('bugfs.Path')

//-------------------------------------------------------------------------------
// Context
//-------------------------------------------------------------------------------

require('bugpack').context("*", function(bugpack) {

    //-------------------------------------------------------------------------------
    // Common Modules
    //-------------------------------------------------------------------------------

    var fs          = require('fs');
    var path        = require('path');
    var tar         = require('tar');
    var zlib        = require('zlib');

    //-------------------------------------------------------------------------------
    // BugPack
    //-------------------------------------------------------------------------------

    var Class       = bugpack.require('Class');
    var Flows       = bugpack.require('Flows');
    var TypeUtil    = bugpack.require('TypeUtil');
    var Path        = bugpack.require('bugfs.Path');

    //-------------------------------------------------------------------------------
    // Simplify References
    //-------------------------------------------------------------------------------

    var $series     = Flows.$series;
    var $task       = Flows.$task;

    //-------------------------------------------------------------------------------
    // Declare Class
    //-------------------------------------------------------------------------------

    /**
     * @class
     * @extends {Path}
     */
    var Tarball = Class.extend(Path, {

        _name: "bugfs.Tarball",

        //-------------------------------------------------------------------------------
        // Public Methods
        //-------------------------------------------------------------------------------

        /**
         * @param {(string|Path)} intoPath
         * @param {?(boolean|function(Error, Path))=} resolveSymlink (defaults to false)
         * @param {?function(Error)} callback
         */
        extractInto: function(intoPath, resolveSymlink, callback) {
            if (TypeUtil.isFunction(resolveSymlink)) {
                callback = resolveSymlink;
            }
            intoPath = TypeUtil.isString(intoPath) ? new Path(intoPath) : intoPath;
            resolveSymlink = TypeUtil.isBoolean(resolveSymlink) ? resolveSymlink : false;

            var _this = this;
            Path.transactionSemaphore.acquire(function() {
                $series([
                    $task(function(flow) {
                        _this._exists(resolveSymlink, function(exists) {
                            if (!exists) {
                                flow.error(new Error("Cannot extract tarball '" + _this.getAbsolutePath() + "' because it " +
                                    "does not exist."));
                            } else {
                                flow.complete();
                            }
                        });
                    }),
                    $task(function(flow) {
                        _this._isTarball(resolveSymlink, function(error, isTarball) {
                            if (!error) {
                                if (isTarball) {
                                    flow.complete();
                                } else {
                                    flow.error(new Error("Cannot extract tarball '" + _this.getAbsolutePath() +
                                        "' because it is not a tarball."));
                                }
                            } else {
                                flow.error(error);
                            }
                        });
                    }),
                    $task(function(flow) {
                        _this.ensurePath(intoPath, function(error) {
                            flow.complete(error);
                        });
                    }),
                    $task(function(flow) {
                        _this._extractInto(intoPath, function(error) {
                            flow.complete(error);
                        });
                    })
                ]).execute(function(error) {
                    Path.transactionSemaphore.release();
                    if (callback) {
                        if (!error) {
                            callback(null);
                        } else {
                            callback(error);
                        }
                    }
                });
            });
        },

        /**
         * @param {(boolean|function(Error, boolean))} resolveSymlink (defaults to false)
         * @param {?function(Error, boolean)=} callback
         */
        isTarball: function(resolveSymlink, callback) {
            if (TypeUtil.isFunction(resolveSymlink)) {
                callback = resolveSymlink;
            }
            resolveSymlink = TypeUtil.isBoolean(resolveSymlink) ? resolveSymlink : false;
            var _this = this;
            Path.transactionSemaphore.acquire(function() {
                _this._isTarball(resolveSymlink, function(error, result) {
                    Path.transactionSemaphore.release();
                    callback(error, result);
                })
            });
        },

        //-------------------------------------------------------------------------------
        // Private Methods
        //-------------------------------------------------------------------------------

        /**
         * @private
         * @param {Path} intoPath
         * @param {function(Error)} callback
         */
        _extractInto: function(intoPath, callback) {
            fs.createReadStream(this.getAbsolutePath())
                .pipe(tar.Extract({ path: intoPath.getAbsolutePath() }))
                .on("error", function (error) {
                    callback(error);
                })
                .on("end", function () {
                    callback(null);
                });
        },

        /**
         * @param {boolean} resolveSymlink
         * @param {function(Error, boolean)} callback
         */
        _isTarball: function(resolveSymlink, callback) {
            var _this = this;
            this._isFile(resolveSymlink, function(error, result) {
                if (!error) {
                    if (result) {

                        //TODO BRN: This is not a very solid check. Should do a better job of validating a tarball.

                        if (_this.getExtName() === "tgz") {
                            callback(null, true);
                        } else {
                            callback(null, false);
                        }
                    }
                } else {
                    callback(error);
                }
            });
        }
    });

    //-------------------------------------------------------------------------------
    // Export
    //-------------------------------------------------------------------------------

    bugpack.export('bugfs.Tarball', Tarball);
});

redpajama_set_name: RedPajamaGithub


text:
// Matt Wells, copyright Jul 2008

#ifndef _TAGDB_H_
#define _TAGDB_H_

#include "Conf.h"       // for setting rdb from Conf file
#include "Rdb.h"
#include "Xml.h"
#include "Url.h"
#include "Loop.h"
#include "DiskPageCache.h"
//#include "CollectionRec.h"
#include "SafeBuf.h"
#include "Msg0.h"

// . now we can store multiple rating by multiple users or algorithms
// . we can use accountability, we can merge sources, etc.
// . we can use time-based merging

bool isTagTypeUnique    ( long tt ) ;
bool isTagTypeIndexable ( long tt ) ;
//bool isTagTypeString ( long tt ) ;
long hexToBinary     ( char *src , char *srcEnd , char *dst , bool decrement );

// . Tag::m_type is this if its a dup in the TagRec
// . so if www.xyz.com has one tag and xyz.com has another, then
//   the xyz.com tag should have its m_type set to TT_DUP, but only if we can only have
//   one of those tag types...
#define TT_DUP 123456

#define TAGDB_KEY key128_t

// a TagRec can contain multiple Tags, even of the same Tag::m_type
class Tag {
 public:

	long  getSize    ( ) { return sizeof(key128_t) + 4 + m_recDataSize; };
	long  getRecSize ( ) { return sizeof(key128_t) + 4 + m_recDataSize; };

	void set ( char *site ,
		   char *tagname ,
		   long  timestamp ,
		   char *user ,
		   long  ip ,
		   char *data ,
		   long  dataSize );

	long print ( ) ;
	bool printToBuf             ( SafeBuf *sb );
	bool printToBufAsAddRequest ( SafeBuf *sb );
	bool printToBufAsXml        ( SafeBuf *sb );
	bool printToBufAsXml2       ( SafeBuf *sb );
	bool printToBufAsHtml       ( SafeBuf *sb , char *prefix );
	bool printToBufAsTagVector  ( SafeBuf *sb );
	// just print the m_data...
	bool printDataToBuf         ( SafeBuf *sb );
	bool isType                 ( char    *t  );
	bool isIndexable            ( ) {
		return isTagTypeIndexable ( m_type ); }
	//( m_dataSize == 1 || isType("meta") ); };
	// for parsing output of printToBuf()
	long setFromBuf            ( char *p , char *pend ) ;
	long setDataFromBuf        ( char *p , char *pend ) ;

	// skip of the username, whose size (including \0) is encoded
	// as the first byte in the m_recData buffer
	char *getTagData     ( ) {return m_buf + *m_buf + 1;};
	long  getTagDataSize ( ) {return m_bufSize - *m_buf - 1; };
	// what user added this tag?
	char *getUser ( ) { return m_buf + 1;};
	// remove the terminating \0 which is included as part of the size
	long  getUserLen ( ) { return *m_buf - 1; };

	// used to determine if one Tag should overwrite the other! if they
	// have the same dedup hash... then yes...
	long getDedupHash ( );

	// tagdb uses 128 bit keys now
	key128_t  m_key;
	long      m_recDataSize;

	// when tag was added/updated
	long     m_timestamp;
	// . ip address of user adding tag
	// . prevent multiple turk voters from same ip!
	long     m_ip;

	// each tag in a TagRec now has a unique id for ez deletion
	//long     m_tagId;

	// the "type" of tag. see the TagDesc array in Tagdb.cpp for a list
	// of all the tag types. m_type is a hash of the type name.
	long     m_type;

	// . m_user[] IS ACTUALLY a 6-byte KEY for another TagRec
	// . this is also a user's name like "mwells"
	// . each user has a TagRec whose FULL key is this userHash
	// . m_user[7] is always \0
	//char     m_user[8];

	long     m_bufSize;
	char     m_buf[0];
};

// . convert "domain_squatter" to ST_DOMAIN_SQUATTER
// . used by CollectionRec::getRegExpNum()
long  getTagTypeFromStr( char *tagTypeName , long tagnameLen = -1 );

// . convert ST_DOMAIN_SQUATTER to "domain_squatter"
char *getTagStrFromType ( long tagType ) ;

// . max # of tags any one site or url can have
// . even AFTER the "inheritance loop"
// . includes the 4 bytes used for size and # of tags
//#define MAX_TAGREC_SIZE 1024

// max "oustanding" msg0 requests sent by TagRec::lookup()
#define MAX_TAGDB_REQUESTS 3

// . the latest version of the TagRec
//#define TAGREC_CURRENT_VERSION 0

class TagRec {

 public:

	TagRec();
	~TagRec();
	void reset();
	void constructor ();

	// . an rdb record is a key, dataSize, then the data
	// . the "data" is al the stuff after "m_dataSize"
	//key_t    m_key;
	//long     m_dataSize;
	//uint16_t m_numTags;
	//char     m_version;
	//char     m_buf [ MAX_TAGREC_SIZE - 12 - 4 - 2 ];

	//char *getKey      () { return (char *)&m_key; };
	//char *getData     () { return (char *)this + 12 + 4; };
	//long  getDataSize () { return m_dataSize; };

	//void  copy        (class TagRec *tp ) {
	//	memcpy ( this , (void *)tp , tp->getSize() ); };

	// includes the 4 byte "header" which consists of the first 2 bytes
	// being the size of the actual tag buffer and the second two bytes
	// being the number of tags in the actual tag buffer
	//long         getSize       ( ) { return 12+4+1+2+m_dataSize; };
	//long           getSize       ( ) { return 12+4+m_dataSize;};

	long           getNumTags    ( );

	long getSize ( ) { return sizeof(TagRec); };

	class Tag *getFirstTag   ( ) {
		if ( m_numListPtrs == 0 ) return NULL;
		return (Tag *)m_listPtrs[0]->m_list;
	}

	bool isEmpty ( ) { return (getFirstTag()==NULL); };

	// lists should be in order of precedence i guess
	class Tag *getNextTag ( class Tag *tag ) {
		// watch out
		if ( ! tag ) return NULL;
		// get rec size
		long recSize = tag->getRecSize();
		// point to current tag
		char *current = (char *)tag;
		// find what list we are in
		long i;
		for ( i = 0 ; i < m_numListPtrs ; i++ ) {
			if ( current <  m_listPtrs[i]->m_list    ) continue;
			if ( current >= m_listPtrs[i]->m_listEnd ) continue;
			break;
		}
		// sanity
		if ( i >= m_numListPtrs ) { char *xx=NULL;*xx=0; }
		// advance
		current += recSize;
		// sanity check
		if ( recSize > 500000 ) { char *xx=NULL;*xx=0;}
		// breach list?
		if ( current < m_listPtrs[i]->m_listEnd) return (Tag *)current;
		// advance list
		i++;
		// breach of lists?
		if ( i >= m_numListPtrs ) return NULL;
		// return that list record then
		return (Tag *)(m_listPtrs[i]->m_list);
	};

	// return the number the tags having particular tag types
	long           getNumTagTypes ( char *tagTypeStr );

	// get a tag from the tagType
	class Tag *getTag        ( char *tagTypeStr );

	class Tag *getTag2       ( long tagType );

	//char *getRecEnd ( ) { return (char *)this + getSize(); };
	//char *getMaxEnd ( ) { return (char *)this + (long)MAX_TAGREC_SIZE; };

	// . for showing under the summary of a search result in PageResults
	// . also for Msg6a
	long print ( ) ;
	bool printToBuf             ( SafeBuf *sb );
	bool printToBufAsAddRequest ( SafeBuf *sb );
	bool printToBufAsXml        ( SafeBuf *sb );
	bool printToBufAsHtml       ( SafeBuf *sb , char *prefix );
	bool printToBufAsTagVector  ( SafeBuf *sb );

	// . make sure not a dup of a pre-existing tag
	// . used by the clock code to not at a clock if already in there
	//   in Msg14.cpp
	Tag *getTag ( char *tagTypeStr , char *dataPtr , long dataSize );

	long getTimestamp ( char *tagTypeStr , long defalt );

	// . functions to act on a site "tag buf", like that in Msg16::m_tagRec
	// . first 2 bytes is size, 2nd to bytes is # of tags, then the tags
	long getLong ( char        *tagTypeStr       ,
		       long         defalt    ,
		       Tag        **bookmark  = NULL ,
		       long        *timeStamp = NULL ,
		       char       **user      = NULL );
	long getLong ( long         tagId     ,
		       long         defalt    ,
		       Tag        **bookmark  = NULL ,
		       long        *timeStamp = NULL ,
		       char       **user      = NULL );

	long long getLongLong ( char        *tagTypeStr,
				long long    defalt    ,
				Tag        **bookmark  = NULL ,
				long        *timeStamp = NULL ,
				char       **user      = NULL );

	char *getString ( char      *tagTypeStr       ,
			  char      *defalt    = NULL ,
			  long      *size      = NULL ,
			  Tag      **bookmark  = NULL ,
			  long      *timestamp = NULL ,
			  char     **user      = NULL );

	// we only store the first 6 chars of "user" into this TagRec, m_buf
	/*
	bool addTag ( char        *tagTypeStr,
		      long         timestamp ,
		      char        *user      ,
		      long         ip        ,
		      char        *data      ,
		      long         dataSize  );

	// we convert the long to a string for you here...
	bool addTag ( char        *tagTypeStr ,
		      long         timestamp  ,
		      char        *user       ,
		      long         ip         ,
		      long         dataAsLong ) {
		char buf[16]; sprintf(buf,"%li",dataAsLong);
		return addTag(tagTypeStr,timestamp,user,ip,buf,
			      gbstrlen(buf)); };

	// same as above
	bool addTag ( Tag *tag );

	// add "negative" tags, so when these tags are added to the
	// TagRec in tagdb, they will delete them
	bool addDelTag ( char *tagTypeStr );

	// now you can specify a unique tag id in the case of multiple tags
	// that have the same tagType and user
	bool removeTags ( char *tagTypeStr , char *user , long tagId = 0 ) ;
	bool removeTags ( long  tagType    , char *user , long tagId = 0 ) ;
	bool removeTag  ( class Tag *rmTag ) ;

	// add/remove all the tags from "tagRec" to our list of tags
	bool addTags ( TagRec *tagRec ) ;
	bool removeTags ( TagRec *tagRec ) ;

	// return false and set g_errno on error
	bool addTags     ( Tag *tags , char *tagEnd , char *bufEnd );
	bool removeTags  ( Tag *tags , char *tagEnd , char *bufEnd );
	bool replaceTags ( Tag *tags , char *tagEnd , char *bufEnd );
	*/

	bool setFromBuf ( char *buf , long bufSize );
	bool serialize ( SafeBuf &dst );

	bool setFromHttpRequest ( HttpRequest *r , TcpSocket *s );

	// use this for setFromBuf()
	SafeBuf m_sbuf;

	// some specified input
	//char  *m_coll;
	Url   *m_url;

	collnum_t m_collnum;

	void    (*m_callback ) ( void *state );
	void     *m_state;

	// hold possible tagdb records
	RdbList m_lists[MAX_TAGDB_REQUESTS];

	// ptrs to lists in the m_lists[] array
	RdbList *m_listPtrs[MAX_TAGDB_REQUESTS];
	long     m_numListPtrs;
};

class Tagdb  {

 public:
	// reset rdb
	void reset();

	// . TODO: specialized cache because to store pre-parsed tagdb recs
	// . TODO: have m_useSeals parameter???
	bool init  ( );
	bool init2 ( long treeMem );

	bool verify ( char *coll );

	//bool convert ( char *coll ) ;

	bool addColl ( char *coll, bool doVerify = true );

	// used by ../rdb/Msg0 and ../rdb/Msg1
	Rdb *getRdb ( ) { return &m_rdb; };

	//key128_t makeKey      ( Url *u , bool isDelete ) ;
	key128_t makeStartKey ( char *site );//Url *u ) ;
	key128_t makeEndKey   ( char *site );//Url *u ) ;

	key128_t makeDomainStartKey ( Url *u ) ;
	key128_t makeDomainEndKey   ( Url *u ) ;

	// . get the serialized TagRec from an RdbList of TagRecs from tagdb
	//   that is the best match for "url"
	char *getRec ( RdbList *list , Url *url , long *recSize ,char* coll,
		       long collLen, RdbList *retList) ;

	DiskPageCache *getDiskPageCache() { return &m_pc; };

	//long getGroupId (key_t *key) {return key->n1 & g_hostdb.m_groupMask;}

	// . dump tagdb to stdout
	// . dump as URL requests so we can re-add with blaster on each host
	// . this replaces dumpTagdb() in main.cpp
	// . sendPageTagdb() will process such URL requests
	void dumpTagdb ( );

	// private:

	// . returns 0 if url is not a suburl of "site"
	long getMatchPoints ( Url *url , Url *site ) ;

	bool setHashTable ( ) ;

	// . we use the cache in here also for caching tagdb records
	//   and "not-founds" stored remotely (net cache)
	Rdb   m_rdb;

	DiskPageCache m_pc;

};

// derive this from tagdb
class Turkdb {
 public:
	void reset();
	bool init  ( );
	bool addColl ( char *coll, bool doVerify = true );
	Rdb *getRdb ( ) { return &m_rdb; };
	Rdb   m_rdb;
	DiskPageCache m_pc;
};

extern class Tagdb  g_tagdb;
extern class Tagdb  g_tagdb2;
extern class Turkdb g_turkdb;
//extern class Tagdb g_sitedb;

bool sendPageTagdb ( TcpSocket *s , HttpRequest *req ) ;

///////////////////////////////////////////////
//
// Msg8a gets TagRecs from Tagdb
//
///////////////////////////////////////////////

// msg8a needs to keep a ptr to the tags it
// generates in the "inheritance loop". so don't
// store more than this! log if we do.
//#define MAX_TAGS 128

//#define MSG8A_MAX_REQUEST_SIZE 2048

// this msg class is for getting AND adding to tagdb
class Msg8a {

 public:

	Msg8a ();
	~Msg8a ();
	void reset();

	// . get records from multiple subdomains of url
	// . calls g_udpServer.sendRequest() on each subdomain of url
	// . all matching records are merge into a final record
	//   i.e. site tags are also propagated accordingly
	// . closest matching "site" is used as the "site" (the site url)
	// . stores the tagRec in your "tagRec"
	bool getTagRec ( Url      *url              ,
			 char     *site , // set to NULL to auto set
			 //char     *coll             ,
			 collnum_t collnum,
			 //bool      useCanonicalName ,
			 bool skipDomainLookup ,
			 long      niceness         ,
			 void     *state            ,
			 void    (* callback)(void *state ),
			 TagRec   *tagRec           ,
			 bool      doInheritance = true ,
			 char      rdbId         = RDB_TAGDB);

	bool launchGetRequests();
	void gotAllReplies ( ) ;

	// some specified input
	//char  *m_coll;
	//long   m_collLen;
	Url   *m_url;
	//bool   m_doFullUrl;
	char   m_rdbId;

	collnum_t m_collnum;

	void    (*m_callback ) ( void *state );
	void     *m_state;

	Msg0    m_msg0s[MAX_TAGDB_REQUESTS];

	key128_t m_siteStartKey ;
	key128_t m_siteEndKey   ;

	// hold possible tagdb records
	//RdbList m_lists[MAX_TAGDB_REQUESTS];

	long m_niceness;

	char *m_dom;
	char *m_hostEnd;
	char *m_p;

	long  m_requests;
	long  m_replies;
	char  m_doneLaunching;

	long  m_errno;

	// we set this for the caller
	TagRec *m_tagRec;

	// hacks for msg6b
	void *m_parent;
	long  m_slotNum;

	// hack for MsgE
	void *m_state2;
	void *m_state3;

	bool  m_doInheritance;
};

/*
void handleRequest9a ( UdpSlot *slot , long niceness ) ;

class Msg9a {

 public:

	Msg9a ();
	~Msg9a();
	void reset() ;
	bool launchAddRequests ( ) ;

	static bool registerHandler ( ) {
		return g_udpServer.registerHandler ( 0x9a, handleRequest9a );};

	// . returns false if blocked, true otherwise
	// . sets errno on error
	// . "sites" is a NULL-terminated list of space-separated urls
	// . if "deleteTags" is false, then the tags will be added to the
	///  the TagRecs specified by the sites in "sites". if a TagRec
	//   does not exist for a given "site" then it will be added just
	//   so we can add the Tags to it. If it does exist, we will
	//   just append the given Tags to it. Tags with the same tagType and
	//   and "user" will be replaced by these Tags in the tagRecPtrs[].
	// . if "deleteTags" is true, then the Tags matching the Tags
	//   given will be removed from each TagRec. If a nonzero timestamp
	//   or a username that does not start with \0 or a non-zero data
	//   is specified in the Tag, then that must match the Tag
	//   being removed as well. In this way we can remove specific
	//   Tags from a list of Tags that share the same m_tagType.
	// . if "deleteTagRecs" is false then the entire TagRec will be removed
	//   from Tagdb. tagRecPtrs must be NULL in this case.
	// . if provided, the ip vector is 1-1 with the sites in "sitesPtrs[]"
	//   and we set the Tag::m_ip with the corresponding
	//   ip given by that. this allows XmlDoc.cpp to "ip stamp" a tag.
	//   and the tag will be rendered invalid by XmlDoc.cpp if the ip of
	//   the site changes from that! in this way we can invalidate tags
	//   when a site changes ownership. assuming it also changes ip!
	// . you can now pass in either "sites" or sitePtrs/numSitePtrs,
	//   whichever is easiest for you...
	bool addTags ( char    *sites                  ,
		       char   **sitePtrs               ,
		       long     numSitePtrs            ,
		       char    *coll                   ,
		       void    *state                  ,
		       void   (*callback)(void *state) ,
		       long     niceness               ,
		       TagRec  *tagRec                 ,
		       bool     nukeTagRecs            ,
		       long    *ipVector               );//= NULL );

	// like above, but we are adding the output of a
	// './gb dump S main 0 -1 1' cmd
	bool addTags ( char    *dumpFile               ,
		       char    *coll                   ,
		       void    *state                  ,
		       void   (*callback)(void *state) ,
		       long     niceness               );

	void    (*m_callback ) ( void *state );
	void     *m_state;

	long  m_errno;
	long  m_requests;
	long  m_replies;

	char *m_requestBuf;
	long  m_requestBufSize;

	char *m_p;
	char *m_pend;

	long  m_niceness;
};
*/

long getY ( long long X , long long *x , long long *y , long n ) ;

#endif

// Lookup order for the url hostname.domainname.com/mydir/mypage.html
// (aka 1.2.3.4/mydir/mypage.html):

//  . hostname.domainname.com/mydir/mypage.html
//  . hostname.domainname.com/mydir/
//  . hostname.domainname.com
//  .          domainname.com/mydir/mypage.html
//  .          domainname.com/mydir/
//  .          domainname.com
//  .          1.2.3.4       /mydir/mypage.html
//  .          1.2.3.4       /mydir/
//  .          1.2.3.4
//  .          1.2.3


text:
package org.beanplant.JavaText.io.commands;

import org.beanplant.JavaText.io.Command;
import org.beanplant.JavaText.io.WidthLimitedOutputStream;

/**
 * The help command. Let's see what happens when users run me!
 *
 * @author the_DJDJ
 */
public class CommandHelp implements Command {

    @Override
    public void execute(String arguments) {

        commandParser.getWorld().getOutputStream().printSpaced(commandParser.getWorld().getMessageBuilder().getHelpMessage(), WidthLimitedOutputStream.BOTH);

    }

}


text:
<?php

namespace EnderLab\MiddleEarth\Application;

use EnderLab\MiddleEarth\Dispatcher\Dispatcher;
use EnderLab\MiddleEarth\Dispatcher\DispatcherInterface;
use EnderLab\MiddleEarth\Dispatcher\DispatcherMiddleware;
use EnderLab\MiddleEarth\Error\ErrorMiddleware;
use EnderLab\MiddleEarth\Logger\LoggerDebugTrait;
use EnderLab\MiddleEarth\Middleware\MiddlewareBuilder;
use EnderLab\MiddleEarth\Router\Route;
use EnderLab\MiddleEarth\Router\RouterInterface;
use EnderLab\MiddleEarth\Router\RouterMiddleware;
use GuzzleHttp\Psr7\ServerRequest;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;

class App extends MiddlewareBuilder
{
    use LoggerDebugTrait;

    const ENV_DEV = 'dev';
    const ENV_TEST = 'test';
    const ENV_PROD = 'prod';
    const ENV_DEBUG = 'debug';

    /**
     * @var string
     */
    private $env = self::ENV_PROD;

    /**
     * @var string
     */
    private $groupPath = '';

    /**
     * @var array
     */
    private $groupMiddleware = [];

    /**
     * App constructor.
     *
     * @param ContainerInterface  $container
     * @param RouterInterface     $router
     * @param DispatcherInterface $dispatcher
     * @param ResponseInterface   $defaultResponse
     */
    public function __construct(
        ContainerInterface $container,
        RouterInterface $router,
        DispatcherInterface $dispatcher,
        ResponseInterface $defaultResponse
    ) {
        parent::__construct($container, $router, $dispatcher, $defaultResponse);
    }

    /**
     * Add route on router by request type.
     *
     * @param $name
     * @param $arguments
     *
     * @throws \BadMethodCallException
     *
     * @return Route
     */
    public function __call($name, $arguments): Route
    {
        switch ($name) {
            case 'get':
            case 'post':
            case 'put':
            case 'patch':
            case 'delete':
            case 'head':
            case 'options':
            case 'any':
                $args = [
                    $arguments[0],
                    (count($arguments) > 1 ? $arguments[1] : null),
                    ('any' === $name ? null : mb_strtoupper($name)),
                    (count($arguments) > 2 ? $arguments[2] : null)
                ];
                break;
            default:
                throw new \BadMethodCallException('Invalid method name "' . $name . '"');
                break;
        }

        return $this->addRoute(...$args);
    }

    /**
     * Add route on router.
     *
     * @param string      $path
     * @param null        $middlewares
     * @param string|null $method
     * @param string|null $name
     *
     * @throws \InvalidArgumentException
     *
     * @return Route
     */
    public function addRoute(
        $path,
        $middlewares = null,
        string $method = null,
        string $name = null
    ): Route {
        if (!$path instanceof Route && null === $middlewares) {
            throw new \InvalidArgumentException('Invalid route config');
        }

        if ($path instanceof Route) {
            $route = $path;
        }

        if (!isset($route)) {
            $path = $this->groupPath . $path;

            if (count($this->groupMiddleware) > 0) {
                $temp = $middlewares;
                $middlewares = $this->groupMiddleware;

                array_push($middlewares, $temp);
            }

            $middlewares = $this->buildMiddleware($middlewares);
            $route = new Route($path, $middlewares, $method, $name);
        }

        $this->router->addRoute($route);

        $this->log(
            $this->container,
            sprintf(
                'App::addRoute - Add route %s %s (%s)',
                implode(',', $route->getMethod()),
                $route->getPath(),
                null === $route->getName() ? 'no name' : $route->getName()
            )
        );

        return $route;
    }

    /**
     * @param string   $path
     * @param callable $callable
     * @param null     $middleware
     *
     * @return App
     */
    public function addGroup(string $path, callable $callable, $middleware = null): self
    {
        $reflection = new \ReflectionFunction($callable);
        $params = $reflection->getParameters();

        if (1 !== count($params)) {
            throw new \InvalidArgumentException('Invalid number argument');
        }

        $arg = $params[0];

        if (!$arg->getClass()->isInstance($this)) {
            throw new \InvalidArgumentException('Callback argument must be implement ' . get_class($this));
        }

        $previousGroupPath = $this->groupPath;
        $this->groupPath = $previousGroupPath . $path;

        if (null !== $middleware) {
            $previousGroupMiddleware = $this->groupMiddleware;
            $this->groupMiddleware[] = $middleware;
        }

        $callable($this);

        $this->groupPath = $previousGroupPath;

        if (null !== $middleware) {
            $this->groupMiddleware = $previousGroupMiddleware;
        }

        return $this;
    }

    /**
     * Add middleware on pipe.
     *
     * @param $path
     * @param null        $middlewares
     * @param string|null $env
     *
     * @return App
     *
     * @internal param bool $first
     */
    public function pipe($path, $middlewares = null, string $env = null): self
    {
        if (null !== $env && $this->env !== $env) {
            return $this;
        }

        if (null === $middlewares) {
            $middlewares = $this->buildMiddleware($path);
            $path = '*';
        }

        if (!$middlewares instanceof MiddlewareInterface) {
            $middlewares = $this->buildMiddleware($middlewares);
        }

        $route = new Route($path, $middlewares);

        $this->dispatcher->pipe($route);

        $this->log(
            $this->container,
            sprintf(
                'App::pipe - Pipe middleware %s %s - %s',
                implode(',', $route->getMethod()),
                $route->getPath(),
                get_class($middlewares)
            )
        );

        return $this;
    }

    /**
     * Start process dispatcher.
     *
     * @param null|ServerRequestInterface $request
     * @param bool                        $returnResponse
     *
     * @return void|\Psr\Http\Message\ResponseInterface
     */
    public function run(?ServerRequestInterface $request = null, bool $returnResponse = false)
    {
        $request = (null !== $request) ? $request : ServerRequest::fromGlobals();
        $request = $request->withAttribute('originalResponse', $this->response);
        $response = $this->dispatcher->handle($request);

        $this->log(
            $this->container,
            sprintf(
                'App::run - Response [%d] %s',
                $response->getStatusCode(),
                $response->getReasonPhrase()
            )
        );

        if (true === $returnResponse) {
            return $response;
        }

        if (PHP_SAPI === 'cli') {
            echo (string) $response->getBody();
        } else {
            \Http\Response\send($response);
        }
    }

    /**
     * @param $env
     */
    public function setEnv($env): void
    {
        if (!in_array(mb_strtolower($env), [self::ENV_PROD, self::ENV_DEV, self::ENV_TEST, self::ENV_DEBUG], true)) {
            throw new \InvalidArgumentException(
                'Environment must be "' . implode(', ', [self::ENV_PROD, self::ENV_DEV, self::ENV_TEST, self::ENV_DEBUG]) . '".'
            );
        }

        $this->env = $env;
    }

    /**
     * Return Container object.
     *
     * @return ContainerInterface
     */
    public function getContainer(): ContainerInterface
    {
        return $this->container;
    }

    /**
     * Return Router object.
     *
     * @return RouterInterface
     */
    public function getRouter(): RouterInterface
    {
        return $this->router;
    }

    /**
     * Return Dispatcher object.
     *
     * @return Dispatcher
     */
    public function getDispatcher(): Dispatcher
    {
        return $this->dispatcher;
    }

    /**
     * Enable error handler.
     *
     * @return App
     */
    public function enableErrorHandler(): self
    {
        $this->pipe(new ErrorMiddleware($this->response));

        return $this;
    }

    /**
     * @return App
     */
    public function enableRouterHandler(): self
    {
        $this->pipe(new RouterMiddleware($this->router, $this->response));

        return $this;
    }

    /**
     * @return App
     */
    public function enableDispatcherHandler(): self
    {
        $this->pipe(new DispatcherMiddleware($this->container, $this->router));

        return $this;
    }
}


text:
static inline unsigned long get_macb_pclk_rate(unsigned int dev_id)
{
	return 83000000;
}


text:
/*
atmosphere/reference/functions_test.cc

This file provides unit tests for the GLSL
functions that implement our atmosphere model. We start with the definition
of some (arbitrary) values for the atmosphere parameters:
*/

#include "atmosphere/reference/functions.h"

#include <limits>
#include <string>

#include "atmosphere/reference/definitions.h"
#include "atmosphere/constants.h"
#include "test/test_case.h"

namespace atmosphere {
namespace reference {

constexpr double kEpsilon = 1e-3;

constexpr SpectralIrradiance kSolarIrradiance =
    123.0 * watt_per_square_meter_per_nm;
constexpr Length kBottomRadius = 1000.0 * km;
constexpr Length kTopRadius = 1500.0 * km;
constexpr Length kScaleHeight = 60.0 * km;
constexpr Length kRayleighScaleHeight = 60.0 * km;
constexpr Length kMieScaleHeight = 30.0 * km;
constexpr ScatteringCoefficient kRayleighScattering = 0.001 / km;
constexpr ScatteringCoefficient kMieScattering = 0.0015 / km;
constexpr ScatteringCoefficient kMieExtinction = 0.002 / km;
constexpr Number kGroundAlbedo = 0.1;

/*

This helper function computes the cosine of the angle between the zenith and
the horizon, at some radius r. We will use it to test rays just above or below
the horizon.
*/

Number CosineOfHorizonZenithAngle(Length r) {
  assert(r >= kBottomRadius);
  return -sqrt(1.0 - (kBottomRadius / r) * (kBottomRadius / r));
}

/*

Some unit tests need a precomputed texture as input, but we don't want to
precompute a whole texture for that, for efficiency reasons. Our solution is to
provide lazily computed textures instead, i.e. textures whose texels are
computed the first time we try to read them. The first type of texture we need
is a lazy transmittance texture (negative values mean "not yet computed"):
*/

class LazyTransmittanceTexture :
    public dimensional::BinaryFunction<TRANSMITTANCE_TEXTURE_WIDTH,
                                       TRANSMITTANCE_TEXTURE_HEIGHT,
                                       DimensionlessSpectrum> {
 public:
  explicit LazyTransmittanceTexture(
      const AtmosphereParameters& atmosphere_parameters)
    : BinaryFunction(DimensionlessSpectrum(-1.0)),
      atmosphere_parameters_(atmosphere_parameters) {
  }

  virtual const DimensionlessSpectrum& Get(int i, int j) const {
    int index = i + j * TRANSMITTANCE_TEXTURE_HEIGHT;
    if (value_[index][0]() < 0.0) {
      value_[index] = ComputeTransmittanceToTopAtmosphereBoundaryTexture(
          atmosphere_parameters_, vec2(i + 0.5, j + 0.5));
    }
    return value_[index];
  }

  void Clear() {
    constexpr unsigned int n =
        TRANSMITTANCE_TEXTURE_WIDTH * TRANSMITTANCE_TEXTURE_HEIGHT;
    for (unsigned int i = 0; i < n; ++i) {
      value_[i] = DimensionlessSpectrum(-1.0);
    }
  }

 private:
  const AtmosphereParameters& atmosphere_parameters_;
};

/*

We also need a lazy single scattering texture:
*/

class LazySingleScatteringTexture :
    public dimensional::TernaryFunction<SCATTERING_TEXTURE_WIDTH,
                                        SCATTERING_TEXTURE_HEIGHT,
                                        SCATTERING_TEXTURE_DEPTH,
                                        IrradianceSpectrum> {
 public:
  LazySingleScatteringTexture(
      const AtmosphereParameters& atmosphere_parameters,
      const TransmittanceTexture& transmittance_texture,
      bool rayleigh)
      : TernaryFunction(IrradianceSpectrum(-watt_per_square_meter_per_nm)),
        atmosphere_parameters_(atmosphere_parameters),
        transmittance_texture_(transmittance_texture),
        rayleigh_(rayleigh) {
  }

  virtual const IrradianceSpectrum& Get(int i, int j, int k) const {
    int index =
        i + SCATTERING_TEXTURE_WIDTH * (j + SCATTERING_TEXTURE_HEIGHT * k);
    if (value_[index][0] < 0.0 * watt_per_square_meter_per_nm) {
      IrradianceSpectrum rayleigh;
      IrradianceSpectrum mie;
      ComputeSingleScatteringTexture(atmosphere_parameters_,
          transmittance_texture_, vec3(i + 0.5, j + 0.5, k + 0.5),
          rayleigh, mie);
      value_[index] = rayleigh_ ? rayleigh : mie;
    }
    return value_[index];
  }

 private:
  const AtmosphereParameters& atmosphere_parameters_;
  const TransmittanceTexture& transmittance_texture_;
  const bool rayleigh_;
};

/*

a lazy multiple scattering texture, for step 1:
*/

class LazyScatteringDensityTexture :
    public dimensional::TernaryFunction<SCATTERING_TEXTURE_WIDTH,
                                        SCATTERING_TEXTURE_HEIGHT,
                                        SCATTERING_TEXTURE_DEPTH,
                                        RadianceDensitySpectrum> {
 public:
  LazyScatteringDensityTexture(
      const AtmosphereParameters& atmosphere_parameters,
      const TransmittanceTexture& transmittance_texture,
      const ReducedScatteringTexture& single_rayleigh_scattering_texture,
      const ReducedScatteringTexture& single_mie_scattering_texture,
      const ScatteringTexture& multiple_scattering_texture,
      const IrradianceTexture& irradiance_texture,
      const int order)
      : TernaryFunction(
            RadianceDensitySpectrum(-watt_per_cubic_meter_per_sr_per_nm)),
        atmosphere_parameters_(atmosphere_parameters),
        transmittance_texture_(transmittance_texture),
        single_rayleigh_scattering_texture_(single_rayleigh_scattering_texture),
        single_mie_scattering_texture_(single_mie_scattering_texture),
        multiple_scattering_texture_(multiple_scattering_texture),
        irradiance_texture_(irradiance_texture),
        order_(order) {
  }

  virtual const RadianceDensitySpectrum& Get(int i, int j, int k) const {
    int index =
        i + SCATTERING_TEXTURE_WIDTH * (j + SCATTERING_TEXTURE_HEIGHT * k);
    if (value_[index][0] < 0.0 * watt_per_cubic_meter_per_sr_per_nm) {
      value_[index] = ComputeScatteringDensityTexture(
          atmosphere_parameters_, transmittance_texture_,
          single_rayleigh_scattering_texture_, single_mie_scattering_texture_,
          multiple_scattering_texture_, irradiance_texture_,
          vec3(i + 0.5, j + 0.5, k + 0.5), order_);
    }
    return value_[index];
  }

 private:
  const AtmosphereParameters& atmosphere_parameters_;
  const TransmittanceTexture& transmittance_texture_;
  const ReducedScatteringTexture& single_rayleigh_scattering_texture_;
  const ReducedScatteringTexture& single_mie_scattering_texture_;
  const ScatteringTexture& multiple_scattering_texture_;
  const IrradianceTexture& irradiance_texture_;
  const int order_;
};

/*

and step 2 of the multiple scattering computations:
*/

class LazyMultipleScatteringTexture :
    public dimensional::TernaryFunction<SCATTERING_TEXTURE_WIDTH,
                                        SCATTERING_TEXTURE_HEIGHT,
                                        SCATTERING_TEXTURE_DEPTH,
                                        RadianceSpectrum> {
 public:
  LazyMultipleScatteringTexture(
      const AtmosphereParameters& atmosphere_parameters,
      const TransmittanceTexture& transmittance_texture,
      const ScatteringDensityTexture& scattering_density_texture)
      : TernaryFunction(RadianceSpectrum(-watt_per_square_meter_per_sr_per_nm)),
        atmosphere_parameters_(atmosphere_parameters),
        transmittance_texture_(transmittance_texture),
        scattering_density_texture_(scattering_density_texture) {
  }

  virtual const RadianceSpectrum& Get(int i, int j, int k) const {
    int index =
        i + SCATTERING_TEXTURE_WIDTH * (j + SCATTERING_TEXTURE_HEIGHT * k);
    if (value_[index][0] < 0.0 * watt_per_square_meter_per_sr_per_nm) {
      Number ignored;
      value_[index] = ComputeMultipleScatteringTexture(atmosphere_parameters_,
          transmittance_texture_, scattering_density_texture_,
          vec3(i + 0.5, j + 0.5, k + 0.5), ignored);
    }
    return value_[index];
  }

 private:
  const AtmosphereParameters& atmosphere_parameters_;
  const TransmittanceTexture& transmittance_texture_;
  const ScatteringDensityTexture& scattering_density_texture_;
};

/*

and, finally, a lazy ground irradiance texture:
*/

class LazyIndirectIrradianceTexture :
    public dimensional::BinaryFunction<IRRADIANCE_TEXTURE_WIDTH,
                                       IRRADIANCE_TEXTURE_HEIGHT,
                                       IrradianceSpectrum> {
 public:
  LazyIndirectIrradianceTexture(
      const AtmosphereParameters& atmosphere_parameters,
      const ReducedScatteringTexture& single_rayleigh_scattering_texture,
      const ReducedScatteringTexture& single_mie_scattering_texture,
      const ScatteringTexture& multiple_scattering_texture,
      int scattering_order)
      : BinaryFunction(IrradianceSpectrum(-watt_per_square_meter_per_nm)),
        atmosphere_parameters_(atmosphere_parameters),
        single_rayleigh_scattering_texture_(single_rayleigh_scattering_texture),
        single_mie_scattering_texture_(single_mie_scattering_texture),
        multiple_scattering_texture_(multiple_scattering_texture),
        scattering_order_(scattering_order) {
  }

  virtual const IrradianceSpectrum& Get(int i, int j) const {
    int index = i + j * IRRADIANCE_TEXTURE_HEIGHT;
    if (value_[index][0] < 0.0 * watt_per_square_meter_per_nm) {
      value_[index] = ComputeIndirectIrradianceTexture(atmosphere_parameters_,
          single_rayleigh_scattering_texture_,
          single_mie_scattering_texture_,
          multiple_scattering_texture_,
          vec2(i + 0.5, j + 0.5),
          scattering_order_);
    }
    return value_[index];
  }

 private:
  const AtmosphereParameters& atmosphere_parameters_;
  const ReducedScatteringTexture& single_rayleigh_scattering_texture_;
  const ReducedScatteringTexture& single_mie_scattering_texture_;
  const ScatteringTexture& multiple_scattering_texture_;
  int scattering_order_;
};

/*

We can now define the unit tests themselves. Each test is an instance of the
following TestCase subclass, which has an
atmosphere_parameters_ field initialized from the above constants.
Note that a new instance of this class is created for each unit test.
*/

class FunctionsTest : public dimensional::TestCase {
 public:
  template<typename T>
  FunctionsTest(const std::string& name, T test)
      : TestCase("FunctionsTest " + name, static_cast<Test>(test)) {
    atmosphere_parameters_.solar_irradiance[0] = kSolarIrradiance;
    atmosphere_parameters_.bottom_radius = kBottomRadius;
    atmosphere_parameters_.top_radius = kTopRadius;
    atmosphere_parameters_.rayleigh_density.layers[1] = DensityProfileLayer(
        0.0 * m, 1.0, -1.0 / kRayleighScaleHeight, 0.0 / m, 0.0);
    atmosphere_parameters_.rayleigh_scattering[0] = kRayleighScattering;
    atmosphere_parameters_.mie_density.layers[1] = DensityProfileLayer(
        0.0 * m, 1.0, -1.0 / kMieScaleHeight, 0.0 / m, 0.0);
    atmosphere_parameters_.mie_scattering[0] = kMieScattering;
    atmosphere_parameters_.mie_extinction[0] = kMieExtinction;
    atmosphere_parameters_.ground_albedo[0] = kGroundAlbedo;
    atmosphere_parameters_.mu_s_min = -1.0;
  }

/*

Distance to the top atmosphere boundary: check that this distance is
$r_\mathrm{top}-r$ for a vertical ray ($\mu=1$), and
$\sqrt{r_\mathrm{top}^2-r^2}$ for a horizontal ray ($\mu=0$).
*/

  void TestDistanceToTopAtmosphereBoundary() {
    constexpr Length r = kBottomRadius * 0.2 + kTopRadius * 0.8;
    // Vertical ray, looking top.
    ExpectNear(
        kTopRadius - r,
        DistanceToTopAtmosphereBoundary(atmosphere_parameters_, r, 1.0),
        1.0 * m);
    // Horizontal ray.
    ExpectNear(
        sqrt(kTopRadius * kTopRadius - r * r),
        DistanceToTopAtmosphereBoundary(atmosphere_parameters_, r, 0.0),
        1.0 * m);
  }

/*

Intersections with the ground: check that a vertical ray does not
intersect the ground, unless it is looking down. Likewise, check that a ray
looking slightly above the horizon ($\mu=\mu_{\mathrm{horiz}}+\epsilon$) does
not intersect the ground, but a ray looking slightly below the horizon
($\mu=\mu_{\mathrm{horiz}}-\epsilon$) does.
*/

  void TestRayIntersectsGround() {
    constexpr Length r = kBottomRadius * 0.9 + kTopRadius * 0.1;
    Number mu_horizon = CosineOfHorizonZenithAngle(r);
    ExpectFalse(RayIntersectsGround(atmosphere_parameters_, r, 1.0));
    ExpectFalse(RayIntersectsGround(
        atmosphere_parameters_, r, mu_horizon + kEpsilon));
    ExpectTrue(RayIntersectsGround(
        atmosphere_parameters_, r, mu_horizon - kEpsilon));
    ExpectTrue(RayIntersectsGround(atmosphere_parameters_, r, -1.0));
  }

/*

Optical length to the top atmosphere boundary: check that for a
vertical ray, looking up, the numerical integration in
ComputeOpticalLengthToTopAtmosphereBoundary gives the expected
result, which can be computed analytically:
$$
\int_r^{r_\mathrm{top}}
  \exp\left(-\frac{x-r_\mathrm{bottom}}{K}\right)\mathrm{d}x =
K\left[\exp\left(-\frac{r-r_\mathrm{bottom}}{K}\right)-
\exp\left(-\frac{r_\mathrm{top}-r_\mathrm{bottom}}{K}\right)\right]
$$
where $K$ is the scale height. Likewise, check that for a constant density
profile the optical length to the top atmosphere boundary is the distance to the
top atmosphere boundary (using a horizontal ray).
*/

  void TestComputeOpticalLengthToTopAtmosphereBoundary() {
    constexpr Length r = kBottomRadius * 0.2 + kTopRadius * 0.8;
    constexpr Length h_r = r - kBottomRadius;
    constexpr Length h_top = kTopRadius - kBottomRadius;
    // Vertical ray, looking top.
    ExpectNear(
        kRayleighScaleHeight * (exp(-h_r / kRayleighScaleHeight) -
            exp(-h_top / kRayleighScaleHeight)),
        ComputeOpticalLengthToTopAtmosphereBoundary(atmosphere_parameters_,
            atmosphere_parameters_.rayleigh_density, r, 1.0),
        1.0 * m);
    // Horizontal ray, no exponential density fall off.
    SetUniformAtmosphere();
    ExpectNear(
        sqrt(kTopRadius * kTopRadius - r * r),
        ComputeOpticalLengthToTopAtmosphereBoundary(atmosphere_parameters_,
            atmosphere_parameters_.rayleigh_density, r, 0.0),
        1.0 * m);
  }

/*

Atmosphere density profiles: check that density profiles with
exponentional, linear or constant density, and one or two layers, are correctly
computed.
*/

  void TestGetProfileDensity() {
    DensityProfile profile;
    // Only one layer, with exponentional density.
    profile.layers[1] =
        DensityProfileLayer(0.0 * m, 1.0, -1.0 / km, 0.0 / km, 0.0);
    ExpectEquals(exp(-2.0), GetProfileDensity(profile, 2.0 * km)());
    // Only one layer, with (clamped) affine density.
    profile.layers[1] =
        DensityProfileLayer(0.0 * m, 0.0, 0.0 / km, -0.5 / km, 1.0);
    ExpectEquals(1.0, GetProfileDensity(profile, 0.0 * km)());
    ExpectEquals(0.5, GetProfileDensity(profile, 1.0 * km)());
    ExpectEquals(0.0, GetProfileDensity(profile, 3.0 * km)());

    // Two layers, with (clamped) affine density.
    profile.layers[0] = DensityProfileLayer(
        25.0 * km, 0.0, 0.0 / km, 1.0 / (15.0 * km), -2.0 / 3.0);
    profile.layers[1] = DensityProfileLayer(
        0.0 * km, 0.0, 0.0 / km, -1.0 / (15.0 * km), 8.0 / 3.0);
    ExpectEquals(0.0, GetProfileDensity(profile, 0.0 * km)());
    ExpectNear(0.0, GetProfileDensity(profile, 10.0 * km)(), kEpsilon);
    ExpectNear(1.0, GetProfileDensity(profile, 25.0 * km)(), kEpsilon);
    ExpectNear(0.0, GetProfileDensity(profile, 40.0 * km)(), kEpsilon);
    ExpectEquals(0.0, GetProfileDensity(profile, 50.0 * km)());
  }

/*

Transmittance to the top atmosphere boundary: check that for a
vertical ray, looking up, the numerical integration in
ComputeTransmittanceToTopAtmosphereBoundary gives the expected
result, which can be computed analytically for an exponential profile (using the
above equation for the optical length) or for a triangular profile (such as the
one used for ozone). Likewise, check that for a horizontal ray, without Mie
scattering, and with a uniform density of air molecules, the optical depth is
the Rayleigh scattering coefficient times the distance to the top atmospere
boundary.
*/

  void TestComputeTransmittanceToTopAtmosphereBoundary() {
    constexpr Length r = kBottomRadius * 0.2 + kTopRadius * 0.8;
    constexpr Length h_r = r - kBottomRadius;
    constexpr Length h_top = kTopRadius - kBottomRadius;
    // Vertical ray, looking up.
    Number rayleigh_optical_depth = kRayleighScattering * kRayleighScaleHeight *
        (exp(-h_r / kRayleighScaleHeight) - exp(-h_top / kRayleighScaleHeight));
    Number mie_optical_depth = kMieExtinction * kMieScaleHeight *
        (exp(-h_r / kMieScaleHeight) - exp(-h_top / kMieScaleHeight));
    ExpectNear(
        exp(-(rayleigh_optical_depth + mie_optical_depth)),
        ComputeTransmittanceToTopAtmosphereBoundary(
            atmosphere_parameters_, r, 1.0)[0],
        Number(kEpsilon));
    // Vertical ray, looking up, no Rayleigh or Mie, only absorption, with a
    // triangular profile (whose integral is equal to 15km).
    atmosphere_parameters_.rayleigh_density.layers[1] = DensityProfileLayer();
    atmosphere_parameters_.mie_density.layers[1] = DensityProfileLayer();
    atmosphere_parameters_.absorption_density.layers[0] = DensityProfileLayer(
        25.0 * km, 0.0, 0.0 / km, 1.0 / (15.0 * km), -2.0 / 3.0);
    atmosphere_parameters_.absorption_density.layers[1] = DensityProfileLayer(
        0.0 * km, 0.0, 0.0 / km, -1.0 / (15.0 * km), 8.0 / 3.0);
    atmosphere_parameters_.absorption_extinction[0] = 0.02 / km;
    ExpectNear(
        exp(-Number(0.02 * 15.0)),
        ComputeTransmittanceToTopAtmosphereBoundary(
            atmosphere_parameters_, kBottomRadius, 1.0)[0],
        Number(kEpsilon));
    // Horizontal ray, uniform atmosphere without aerosols.
    SetUniformAtmosphere();
    RemoveAerosols();
    ExpectNear(
        exp(-kRayleighScattering * sqrt(kTopRadius * kTopRadius - r * r)),
        ComputeTransmittanceToTopAtmosphereBoundary(
            atmosphere_parameters_, r, 0.0)[0],
        Number(kEpsilon));
  }

/*

Texture coordinates: check that for a texture of size $n$, the center
of texel 0 (at texel coordinate $0.5/n$) is mapped to 0, and that the center of
texel $n-1$ (at texel coordinate $(n-0.5)/n$) is mapped to 1 (and vice-versa).
Finally, check that the mapping function and its inverse are really inverse of
each other (i.e. their composition should give the identity function).
*/

  void TestGetTextureCoordFromUnitRange() {
    ExpectNear(0.5 / 10.0, GetTextureCoordFromUnitRange(0.0, 10)(), kEpsilon);
    ExpectNear(9.5 / 10.0, GetTextureCoordFromUnitRange(1.0, 10)(), kEpsilon);

    ExpectNear(0.0, GetUnitRangeFromTextureCoord(0.5 / 10.0, 10)(), kEpsilon);
    ExpectNear(1.0, GetUnitRangeFromTextureCoord(9.5 / 10.0, 10)(), kEpsilon);

    ExpectNear(1.0 / 3.0, GetUnitRangeFromTextureCoord(
        GetTextureCoordFromUnitRange(1.0 / 3.0, 10), 10)(), kEpsilon);
  }

/*

Mapping to transmittance texture coordinates: check that the boundary
values of $r$ ($r_\mathrm{bottom}$ and $r_\mathrm{top}$) and $\mu$
($\mu_\mathrm{horiz}$ and $1$) are mapped to the centers of the boundary texels
of the transmittance texture.
*/

  void TestGetTransmittanceTextureUvFromRMu() {
    vec2 uv = GetTransmittanceTextureUvFromRMu(
        atmosphere_parameters_, kTopRadius, 1.0);
    ExpectNear(0.5 / TRANSMITTANCE_TEXTURE_WIDTH, uv.x(), kEpsilon);
    ExpectNear(1.0 - 0.5 / TRANSMITTANCE_TEXTURE_HEIGHT, uv.y(), kEpsilon);

    Number top_mu_horizon = CosineOfHorizonZenithAngle(kTopRadius);
    uv = GetTransmittanceTextureUvFromRMu(
        atmosphere_parameters_, kTopRadius, top_mu_horizon);
    ExpectNear(1.0 - 0.5 / TRANSMITTANCE_TEXTURE_WIDTH, uv.x(), kEpsilon);
    ExpectNear(1.0 - 0.5 / TRANSMITTANCE_TEXTURE_HEIGHT, uv.y(), kEpsilon);

    uv = GetTransmittanceTextureUvFromRMu(
        atmosphere_parameters_, kBottomRadius, 1.0);
    ExpectNear(0.5 / TRANSMITTANCE_TEXTURE_WIDTH, uv.x(), kEpsilon);
    ExpectNear(0.5 / TRANSMITTANCE_TEXTURE_HEIGHT, uv.y(), kEpsilon);

    uv = GetTransmittanceTextureUvFromRMu(
        atmosphere_parameters_, kBottomRadius, 0.0);
    ExpectNear(1.0 - 0.5 / TRANSMITTANCE_TEXTURE_WIDTH, uv.x(), kEpsilon);
    ExpectNear(0.5 / TRANSMITTANCE_TEXTURE_HEIGHT, uv.y(), kEpsilon);
  }

/*

Mapping from transmittance texture coordinates: check that the centers
of the boundary texels of the transmittance texture are mapped to the boundary
values of $r$ ($r_\mathrm{bottom}$ and $r_\mathrm{top}$) and $\mu$
($\mu_{\mathrm{horiz}}$ and $1$). Finally, check that the mapping function and
its inverse are really inverse of each other (i.e. their composition should give
the identity function).
*/

  void TestGetRMuFromTransmittanceTextureUv() {
    Length r;
    Number mu;
    GetRMuFromTransmittanceTextureUv(
        atmosphere_parameters_,
        vec2(0.5 / TRANSMITTANCE_TEXTURE_WIDTH,
             1.0 - 0.5 / TRANSMITTANCE_TEXTURE_HEIGHT),
        r, mu);
    ExpectNear(kTopRadius, r, 1.0 * m);
    ExpectNear(1.0, mu(), kEpsilon);

    GetRMuFromTransmittanceTextureUv(
        atmosphere_parameters_,
        vec2(1.0 - 0.5 / TRANSMITTANCE_TEXTURE_WIDTH,
             1.0 - 0.5 / TRANSMITTANCE_TEXTURE_HEIGHT),
        r, mu);
    ExpectNear(kTopRadius, r, 1.0 * m);
    ExpectNear(
        CosineOfHorizonZenithAngle(kTopRadius)(),
        mu(),
        kEpsilon);

    GetRMuFromTransmittanceTextureUv(
        atmosphere_parameters_,
        vec2(0.5 / TRANSMITTANCE_TEXTURE_WIDTH,
             0.5 / TRANSMITTANCE_TEXTURE_HEIGHT),
        r, mu);
    ExpectNear(kBottomRadius, r, 1.0 * m);
    ExpectNear(1.0, mu(), kEpsilon);

    GetRMuFromTransmittanceTextureUv(
        atmosphere_parameters_,
        vec2(1.0 - 0.5 / TRANSMITTANCE_TEXTURE_WIDTH,
             0.5 / TRANSMITTANCE_TEXTURE_HEIGHT),
        r, mu);
    ExpectNear(kBottomRadius, r, 1.0 * m);
    ExpectNear(0.0, mu(), kEpsilon);

    GetRMuFromTransmittanceTextureUv(
        atmosphere_parameters_,
        GetTransmittanceTextureUvFromRMu(atmosphere_parameters_,
            kBottomRadius * 0.2 + kTopRadius * 0.8, 0.25),
        r, mu);
    ExpectNear(kBottomRadius * 0.2 + kTopRadius * 0.8, r, 1.0 * m);
    ExpectNear(0.25, mu(), kEpsilon);
  }

/*

Transmittance texture: check that we get the same transmittance to the
top atmosphere boundary (more or less $\epsilon$) whether we compute it directly
with ComputeTransmittanceToTopAtmosphereBoundary, or via a
bilinearly interpolated lookup in the precomputed transmittance texture.
*/

  void TestGetTransmittanceToTopAtmosphereBoundary() {
    LazyTransmittanceTexture transmittance_texture(atmosphere_parameters_);

    const Length r = kBottomRadius * 0.2 + kTopRadius * 0.8;
    const Number mu = 0.4;
    ExpectNear(
        ComputeTransmittanceToTopAtmosphereBoundary(
            atmosphere_parameters_, r, mu)[0],
        GetTransmittanceToTopAtmosphereBoundary(
            atmosphere_parameters_, transmittance_texture, r, mu)[0],
        Number(kEpsilon));
  }

/*

Transmittance texture: check that GetTransmittance (which
combines two bilinearly interpolated lookups in the precomputed transmittance
texture) gives the expected result, in some cases where this result can be
computed analytically (when there are no aerosols and when the density of air
molecules is uniform, the optical depth is simply the Rayleigh scattering
coefficient times the distance travelled in the atmosphere).
*/

  void TestComputeAndGetTransmittance() {
    SetUniformAtmosphere();
    RemoveAerosols();
    LazyTransmittanceTexture transmittance_texture(atmosphere_parameters_);

    const Length r = kBottomRadius * 0.2 + kTopRadius * 0.8;
    const Length d = (kTopRadius - kBottomRadius) * 0.1;
    // Horizontal ray, from bottom atmosphere boundary.
    ExpectNear(
        exp(-kRayleighScattering * d),
        GetTransmittance(atmosphere_parameters_, transmittance_texture,
            kBottomRadius, 0.0, d, false /* ray_intersects_ground */)[0],
        Number(kEpsilon));
    // Almost vertical ray, looking up.
    ExpectNear(
        exp(-kRayleighScattering * d),
        GetTransmittance(
            atmosphere_parameters_, transmittance_texture, r, 0.7, d,
            false /* ray_intersects_ground */)[0],
        Number(kEpsilon));
    // Almost vertical ray, looking down.
    ExpectNear(
        exp(-kRayleighScattering * d),
        GetTransmittance(
            atmosphere_parameters_, transmittance_texture, r, -0.7, d,
            RayIntersectsGround(atmosphere_parameters_, r, -0.7))[0],
        Number(kEpsilon));
  }

/*

Single scattering integrand: check that the computation in
ComputeSingleScatteringIntegrand (which uses a precomputed
transmittance texture) gives the expected result, in 3 cases where this result
can be computed analytically:

vertical ray, looking up from the ground, sun at the zenith, scattering at
$r$. The integrand is then the product of:

the transmittance from the bottom to the top of the atmosphere. This
  involves the Rayleigh and Mie optical depths from the bottom to the top of the
  atmosphere, which have the form
  $$k_{ext}\int_{r_\mathrm{bottom}}^{r_\mathrm{top}}
      \exp\left(-\frac{x-r_\mathrm{bottom}}{K}\right)\mathrm{d}x =
      k_{ext}K\left[1-\exp\left(
          -\frac{r_\mathrm{top}-r_\mathrm{bottom}}{K}\right)\right]$$

the (relative) number density at $r$:
  $\exp(-(r-r_\mathrm{bottom})/K))$.

vertical ray, looking down from the top atmosphere boundary, sun at the
zenith, scattering at $r$. The integrand is then the product of:

the transmittance from the top of the atmosphere to $r$, and back. This
  involves the Rayleigh and Mie optical depths from the top of the atmosphere to
  $r$ (times $2$), which have the form
  $$k_{ext}K\left[
          \exp\left(-\frac{r-r_\mathrm{bottom}}{K}\right)
          -\exp\left(-\frac{r_\mathrm{top}-r_\mathrm{bottom}}{K}\right)
      \right]$$

the (relative) number density at $r$:
  $\exp(-(r-r_\mathrm{bottom})/K))$.

horizontal ray, from the ground, sun at the horizon, scattering at distance
$d$ from the viewer, without aerosols and with a uniform density of air
molecules. The integrand is then simply the transmittance for a horizontal ray
from the ground to the top atmosphere boundary (which has already been computed
in TestComputeTransmittanceToTopAtmosphereBoundary).

*/

  void TestComputeSingleScatteringIntegrand() {
    LazyTransmittanceTexture transmittance_texture(atmosphere_parameters_);

    // Vertical ray, from bottom to top atmosphere boundary, scattering at
    // middle of ray, scattering angle equal to 0.
    const Length h_top = kTopRadius - kBottomRadius;
    const Length h = h_top / 2.0;
    DimensionlessSpectrum rayleigh;
    DimensionlessSpectrum mie;
    ComputeSingleScatteringIntegrand(
        atmosphere_parameters_, transmittance_texture,
        kBottomRadius, 1.0, 1.0, 1.0, h, false, rayleigh, mie);
    Number rayleigh_optical_depth = kRayleighScattering * kRayleighScaleHeight *
        (1.0 - exp(-h_top / kRayleighScaleHeight));
    Number mie_optical_depth = kMieExtinction * kMieScaleHeight *
        (1.0 - exp(-h_top / kMieScaleHeight));
    ExpectNear(
        exp(-rayleigh_optical_depth - mie_optical_depth) *
            exp(-h / kRayleighScaleHeight),
        rayleigh[0],
        Number(kEpsilon));
    ExpectNear(
        exp(-rayleigh_optical_depth - mie_optical_depth) *
            exp(-h / kMieScaleHeight),
        mie[0],
        Number(kEpsilon));

    // Vertical ray, top to middle of atmosphere, scattering angle 180 degrees.
    ComputeSingleScatteringIntegrand(
        atmosphere_parameters_, transmittance_texture,
        kTopRadius, -1.0, 1.0, -1.0, h, true, rayleigh, mie);
    rayleigh_optical_depth = 2.0 * kRayleighScattering * kRayleighScaleHeight *
        (exp(-h / kRayleighScaleHeight) - exp(-h_top / kRayleighScaleHeight));
    mie_optical_depth = 2.0 * kMieExtinction * kMieScaleHeight *
        (exp(-h / kMieScaleHeight) - exp(-h_top / kMieScaleHeight));
    ExpectNear(
        exp(-rayleigh_optical_depth - mie_optical_depth) *
            exp(-h / kRayleighScaleHeight),
        rayleigh[0],
        Number(kEpsilon));
    ExpectNear(
        exp(-rayleigh_optical_depth - mie_optical_depth) *
            exp(-h / kMieScaleHeight),
        mie[0],
        Number(kEpsilon));

    // Horizontal ray, from bottom to top atmosphere boundary, scattering at
    // 50km, scattering angle equal to 0, uniform atmosphere, no aerosols.
    transmittance_texture.Clear();
    SetUniformAtmosphere();
    RemoveAerosols();
    ComputeSingleScatteringIntegrand(
        atmosphere_parameters_, transmittance_texture,
        kBottomRadius, 0.0, 0.0, 1.0, 50.0 * km, false, rayleigh, mie);
    rayleigh_optical_depth = kRayleighScattering * sqrt(
        kTopRadius * kTopRadius - kBottomRadius * kBottomRadius);
    ExpectNear(
        exp(-rayleigh_optical_depth),
        rayleigh[0],
        Number(kEpsilon));
  }

/*

Distance to the nearest atmosphere boundary: check that this distance
is $r_\mathrm{top}-r$ for a vertical ray looking up ($\mu=1$),
$\sqrt{r_\mathrm{top}^2-r^2}$ for a horizontal ray ($\mu=0$), and
$r-r_\mathrm{bottom}$ for a vertical ray looking down ($\mu=-1$).
*/

  void TestDistanceToNearestAtmosphereBoundary() {
    constexpr Length r = kBottomRadius * 0.2 + kTopRadius * 0.8;
    // Vertical ray, looking top.
    ExpectNear(
        kTopRadius - r,
        DistanceToNearestAtmosphereBoundary(
            atmosphere_parameters_, r, 1.0,
            RayIntersectsGround(atmosphere_parameters_, r, 1.0)),
        1.0 * m);
    // Horizontal ray.
    ExpectNear(
        sqrt(kTopRadius * kTopRadius - r * r),
        DistanceToNearestAtmosphereBoundary(
            atmosphere_parameters_, r, 0.0,
            RayIntersectsGround(atmosphere_parameters_, r, 0.0)),
        1.0 * m);
    // Vertical ray, looking down.
    ExpectNear(
        r - kBottomRadius,
        DistanceToNearestAtmosphereBoundary(
            atmosphere_parameters_, r, -1.0,
            RayIntersectsGround(atmosphere_parameters_, r, -1.0)),
        1.0 * m);
  }

/*

Single scattering: check that the numerical integration in
ComputeSingleScattering gives the expected result, in 2 cases where
the integral can be computed analytically:

vertical ray, looking up from the ground, sun at the zenith. The single
scattering integral has the form (where we omitted the solar irradiance and the
Mie terms to simplify the expression; see also
TestComputeSingleScatteringIntegrand):
$$
k_{sca}\int_{r_\mathrm{bottom}}^{r_\mathrm{top}}
    \exp\left(-k_{ext}K\left[1-
            \exp\left(-\frac{r_\mathrm{top}-r_\mathrm{bottom}}{K}\right)
        \right]\right)
    \exp\left(-\frac{r-r_\mathrm{bottom}}{K}\right)\mathrm{d}r
$$
which is equal to
$$
k_{sca}\exp\left(-k_{ext}K\left[1-
    \exp\left(-\frac{r_\mathrm{top}-r_\mathrm{bottom}}{K}\right)
        \right]\right)
K\left[
    1-\exp\left(-\frac{r_\mathrm{top}-r_\mathrm{bottom}}{K}\right)
\right]
$$

vertical ray, looking down from the top atmosphere boundary, sun at the
zenith, no aerosols. The single scattering integral has the form (where we
omitted the solar irradiance; see also
TestComputeSingleScatteringIntegrand):
$$
k_{sca}\int_{r_\mathrm{bottom}}^{r_\mathrm{top}}
    \exp\left(-2k_{sca}K\left[
            \exp\left(-\frac{r-r_\mathrm{bottom}}{K}\right)-
            \exp\left(-\frac{r_\mathrm{top}-r_\mathrm{bottom}}{K}\right)
        \right]\right)\\
    \exp\left(-\frac{r-r_\mathrm{bottom}}{K}\right)\mathrm{d}r
$$
which, using the change of variables $u=\exp(-(r-r_\mathrm{bottom})/K)$,
gives
$$
\frac{1}{2}\left(1-\exp\left(-2k_{sca}K\left(
        1-\exp\left(-\frac{r_\mathrm{top}-r_\mathrm{bottom}}{K}\right)
    \right)\right)\right)
$$

*/

  void TestComputeSingleScattering() {
    LazyTransmittanceTexture transmittance_texture(atmosphere_parameters_);

    // Vertical ray, from bottom atmosphere boundary, scattering angle 0.
    const Length h_top = kTopRadius - kBottomRadius;
    IrradianceSpectrum rayleigh;
    IrradianceSpectrum mie;
    ComputeSingleScattering(
        atmosphere_parameters_, transmittance_texture,
        kBottomRadius, 1.0, 1.0, 1.0, false, rayleigh, mie);
    Number rayleigh_optical_depth = kRayleighScattering * kRayleighScaleHeight *
        (1.0 - exp(-h_top / kRayleighScaleHeight));
    Number mie_optical_depth = kMieExtinction * kMieScaleHeight *
        (1.0 - exp(-h_top / kMieScaleHeight));
    // The relative error is about 1% here.
    ExpectNear(
        Number(1.0),
        rayleigh[0] / (kSolarIrradiance * rayleigh_optical_depth *
            exp(-rayleigh_optical_depth - mie_optical_depth)),
        Number(10.0 * kEpsilon));
    ExpectNear(
        Number(1.0),
        mie[0] / (kSolarIrradiance * mie_optical_depth * kMieScattering /
            kMieExtinction * exp(-rayleigh_optical_depth - mie_optical_depth)),
        Number(10.0 * kEpsilon));

    // Vertical ray, from top atmosphere boundary, scattering angle 180 degrees,
    // no aerosols.
    transmittance_texture.Clear();
    RemoveAerosols();
    ComputeSingleScattering(
        atmosphere_parameters_, transmittance_texture,
        kTopRadius, -1.0, 1.0, -1.0, true, rayleigh, mie);
    ExpectNear(
        Number(1.0),
        rayleigh[0] / (kSolarIrradiance *
            0.5 * (1.0 - exp(-2.0 * kRayleighScaleHeight * kRayleighScattering *
                (1.0 - exp(-h_top / kRayleighScaleHeight))))),
        Number(2.0 * kEpsilon));
    ExpectNear(0.0, mie[0].to(watt_per_square_meter_per_nm), kEpsilon);
  }

/*

Rayleigh and Mie phase functions: check that the integral of these
phase functions over all solid angles gives $1$.
*/

  void TestPhaseFunctions() {
    Number rayleigh_integral = 0.0;
    Number mie_integral = 0.0;
    const unsigned int N = 100;
    for (unsigned int i = 0; i < N; ++i) {
      Angle theta = (i + 0.5) * pi / N;
      SolidAngle domega = sin(theta) * (PI / N) * (2.0 * PI) * sr;
      rayleigh_integral =
          rayleigh_integral + RayleighPhaseFunction(cos(theta)) * domega;
      mie_integral =
          mie_integral + MiePhaseFunction(0.8, cos(theta)) * domega;
    }
    ExpectNear(1.0, rayleigh_integral(), 2.0 * kEpsilon);
    ExpectNear(1.0, mie_integral(), 2.0 * kEpsilon);
  }

/*

Mapping to scattering texture coordinates: check that the boundary
values of $r$ ($r_\mathrm{top}$, $r_\mathrm{bottom}$), $\mu$ ($-1$,
$\mu_{\mathrm{horiz}}$ and $1$), and $\mu_s$ and $\nu$ ($-1$ and $1$), are
mapped to the centers of the boundary texels of the scattering texture.
*/

  void TestGetScatteringTextureUvwzFromRMuMuSNu() {
    ExpectNear(
        0.5 / SCATTERING_TEXTURE_R_SIZE,
        GetScatteringTextureUvwzFromRMuMuSNu(
            atmosphere_parameters_, kBottomRadius, 0.0, 0.0, 0.0, false).w(),
        kEpsilon);
    ExpectNear(
        1.0 - 0.5 / SCATTERING_TEXTURE_R_SIZE,
        GetScatteringTextureUvwzFromRMuMuSNu(
            atmosphere_parameters_, kTopRadius, 0.0, 0.0, 0.0, false).w(),
        kEpsilon);

    const Length r = (kTopRadius + kBottomRadius) / 2.0;
    const Number mu = CosineOfHorizonZenithAngle(r);
    ExpectNear(
        0.5 / SCATTERING_TEXTURE_MU_SIZE,
        GetScatteringTextureUvwzFromRMuMuSNu(
            atmosphere_parameters_, r, mu, 0.0, 0.0, true).z(),
        kEpsilon);
    ExpectNear(
        1.0 - 0.5 / SCATTERING_TEXTURE_MU_SIZE,
        GetScatteringTextureUvwzFromRMuMuSNu(
            atmosphere_parameters_, r, mu, 0.0, 0.0, false).z(),
        kEpsilon);
    ExpectTrue(GetScatteringTextureUvwzFromRMuMuSNu(
        atmosphere_parameters_, r, -1.0, 0.0, 0.0, true).z() < 0.5);
    ExpectTrue(GetScatteringTextureUvwzFromRMuMuSNu(
        atmosphere_parameters_, r, 1.0, 0.0, 0.0, false).z() > 0.5);

    ExpectNear(
        0.5 / SCATTERING_TEXTURE_MU_S_SIZE,
        GetScatteringTextureUvwzFromRMuMuSNu(
            atmosphere_parameters_, kBottomRadius, 0.0, -1.0, 0.0, false).y(),
        kEpsilon);
    ExpectNear(
        1.0 - 0.5 / SCATTERING_TEXTURE_MU_S_SIZE,
        GetScatteringTextureUvwzFromRMuMuSNu(
            atmosphere_parameters_, kBottomRadius, 0.0, 1.0, 0.0, false).y(),
        kEpsilon);

    ExpectNear(
        0.5 / SCATTERING_TEXTURE_MU_S_SIZE,
        GetScatteringTextureUvwzFromRMuMuSNu(
            atmosphere_parameters_, kTopRadius, 0.0, -1.0, 0.0, false).y(),
        kEpsilon);
    ExpectNear(
        1.0 - 0.5 / SCATTERING_TEXTURE_MU_S_SIZE,
        GetScatteringTextureUvwzFromRMuMuSNu(
            atmosphere_parameters_, kTopRadius, 0.0, 1.0, 0.0, false).y(),
        kEpsilon);

    ExpectNear(
        0.0,
        GetScatteringTextureUvwzFromRMuMuSNu(
            atmosphere_parameters_, kBottomRadius, 0.0, 0.0, -1.0, false).x(),
        kEpsilon);
    ExpectNear(
        1.0,
        GetScatteringTextureUvwzFromRMuMuSNu(
            atmosphere_parameters_, kBottomRadius, 0.0, 0.0, 1.0, false).x(),
        kEpsilon);
  }

/*

Mapping from scattering texture coordinates: check that the centers of
the boundary texels of the scattering texture are mapped to the boundary values
of $r$ ($r_\mathrm{top}$, $r_\mathrm{bottom}$), $\mu$ ($-1$,
$\mu_{\mathrm{horiz}}$ and $1$), and $\mu_s$ and $\nu$ ($-1$ and $1$). Finally,
check that the mapping function and its inverse are really inverse of each other
(i.e. their composition should give the identity function).
*/

  void TestGetRMuMuSNuFromScatteringTextureUvwz() {
    Length r;
    Number mu;
    Number mu_s;
    Number nu;
    bool ray_r_mu_intersects_ground;
    GetRMuMuSNuFromScatteringTextureUvwz(atmosphere_parameters_,
        vec4(0.0,
             0.5 / SCATTERING_TEXTURE_MU_S_SIZE,
             0.5 / SCATTERING_TEXTURE_MU_SIZE,
             0.5 / SCATTERING_TEXTURE_R_SIZE),
        r, mu, mu_s, nu, ray_r_mu_intersects_ground);
    ExpectNear(kBottomRadius, r, 1.0 * m);
    GetRMuMuSNuFromScatteringTextureUvwz(atmosphere_parameters_,
        vec4(0.0,
             0.5 / SCATTERING_TEXTURE_MU_S_SIZE,
             0.5 / SCATTERING_TEXTURE_MU_SIZE,
             1.0 - 0.5 / SCATTERING_TEXTURE_R_SIZE),
        r, mu, mu_s, nu, ray_r_mu_intersects_ground);
    ExpectNear(kTopRadius, r, 1.0 * m);

    GetRMuMuSNuFromScatteringTextureUvwz(atmosphere_parameters_,
        vec4(0.0,
             0.5 / SCATTERING_TEXTURE_MU_S_SIZE,
             0.5 / SCATTERING_TEXTURE_MU_SIZE + kEpsilon,
             0.5),
        r, mu, mu_s, nu, ray_r_mu_intersects_ground);
    const Number mu_horizon = CosineOfHorizonZenithAngle(r);
    ExpectNear(mu_horizon, mu, Number(kEpsilon));
    ExpectTrue(mu <= mu_horizon);
    ExpectTrue(ray_r_mu_intersects_ground);
    GetRMuMuSNuFromScatteringTextureUvwz(atmosphere_parameters_,
        vec4(0.0,
             0.5 / SCATTERING_TEXTURE_MU_S_SIZE,
             1.0 - 0.5 / SCATTERING_TEXTURE_MU_SIZE - kEpsilon,
             0.5),
        r, mu, mu_s, nu, ray_r_mu_intersects_ground);
    ExpectNear(mu_horizon, mu, Number(5.0 * kEpsilon));
    ExpectTrue(mu >= mu_horizon);
    ExpectFalse(ray_r_mu_intersects_ground);

    GetRMuMuSNuFromScatteringTextureUvwz(atmosphere_parameters_,
        vec4(0.0,
             0.5 / SCATTERING_TEXTURE_MU_S_SIZE,
             0.5 / SCATTERING_TEXTURE_MU_SIZE,
             0.5 / SCATTERING_TEXTURE_R_SIZE),
        r, mu, mu_s, nu, ray_r_mu_intersects_ground);
    ExpectNear(-1.0, mu_s(), kEpsilon);
    GetRMuMuSNuFromScatteringTextureUvwz(atmosphere_parameters_,
        vec4(0.0,
             1.0 - 0.5 / SCATTERING_TEXTURE_MU_S_SIZE,
             0.5 / SCATTERING_TEXTURE_MU_SIZE,
             0.5 / SCATTERING_TEXTURE_R_SIZE),
        r, mu, mu_s, nu, ray_r_mu_intersects_ground);
    ExpectNear(1.0, mu_s(), kEpsilon);

    GetRMuMuSNuFromScatteringTextureUvwz(atmosphere_parameters_,
        vec4(0.0,
             0.5 / SCATTERING_TEXTURE_MU_S_SIZE,
             0.5 / SCATTERING_TEXTURE_MU_SIZE,
             0.5 / SCATTERING_TEXTURE_R_SIZE),
        r, mu, mu_s, nu, ray_r_mu_intersects_ground);
    ExpectNear(-1.0, nu(), kEpsilon);
    GetRMuMuSNuFromScatteringTextureUvwz(atmosphere_parameters_,
        vec4(1.0,
             0.5 / SCATTERING_TEXTURE_MU_S_SIZE,
             0.5 / SCATTERING_TEXTURE_MU_SIZE,
             0.5 / SCATTERING_TEXTURE_R_SIZE),
        r, mu, mu_s, nu, ray_r_mu_intersects_ground);
    ExpectNear(1.0, nu(), kEpsilon);

    GetRMuMuSNuFromScatteringTextureUvwz(atmosphere_parameters_,
        GetScatteringTextureUvwzFromRMuMuSNu(atmosphere_parameters_,
            kBottomRadius, -1.0, 1.0, -1.0, true),
        r, mu, mu_s, nu, ray_r_mu_intersects_ground);
    ExpectNear(kBottomRadius, r, 1.0 * m);
    ExpectNear(-1.0, mu(), kEpsilon);
    ExpectNear(1.0, mu_s(), kEpsilon);
    ExpectNear(-1.0, nu(), kEpsilon);
    ExpectTrue(ray_r_mu_intersects_ground);

    GetRMuMuSNuFromScatteringTextureUvwz(atmosphere_parameters_,
        GetScatteringTextureUvwzFromRMuMuSNu(atmosphere_parameters_,
            kTopRadius, -1.0, 1.0, -1.0, true),
        r, mu, mu_s, nu, ray_r_mu_intersects_ground);
    ExpectNear(kTopRadius, r, 1.0 * m);
    ExpectNear(-1.0, mu(), kEpsilon);
    ExpectNear(1.0, mu_s(), kEpsilon);
    ExpectNear(-1.0, nu(), kEpsilon);
    ExpectTrue(ray_r_mu_intersects_ground);

    GetRMuMuSNuFromScatteringTextureUvwz(atmosphere_parameters_,
        GetScatteringTextureUvwzFromRMuMuSNu(atmosphere_parameters_,
            (kBottomRadius + kTopRadius) / 2.0, 0.2, 0.3, 0.4, false),
        r, mu, mu_s, nu, ray_r_mu_intersects_ground);
    ExpectNear((kBottomRadius + kTopRadius) / 2.0, r, 1.0 * m);
    ExpectNear(0.2, mu(), kEpsilon);
    ExpectNear(0.3, mu_s(), kEpsilon);
    ExpectNear(0.4, nu(), kEpsilon);
    ExpectFalse(ray_r_mu_intersects_ground);
  }

/*

Single scattering texture: check that we get the same single
scattering value (more or less $\epsilon$) whether we compute it directly
with ComputeSingleScattering, or via a
quadrilinearly interpolated lookup in the precomputed single scattering texture.
*/

  void TestComputeAndGetSingleScattering() {
    LazyTransmittanceTexture transmittance_texture(atmosphere_parameters_);
    LazySingleScatteringTexture single_rayleigh_scattering_texture(
        atmosphere_parameters_, transmittance_texture, true);
    LazySingleScatteringTexture single_mie_scattering_texture(
        atmosphere_parameters_, transmittance_texture, false);

    // Vertical ray, from bottom atmosphere boundary, scattering angle 0.
    IrradianceSpectrum rayleigh = GetScattering(
        atmosphere_parameters_, single_rayleigh_scattering_texture,
        kBottomRadius, 1.0, 1.0, 1.0, false);
    IrradianceSpectrum mie = GetScattering(
        atmosphere_parameters_, single_mie_scattering_texture,
        kBottomRadius, 1.0, 1.0, 1.0, false);
    IrradianceSpectrum expected_rayleigh;
    IrradianceSpectrum expected_mie;
    ComputeSingleScattering(
        atmosphere_parameters_, transmittance_texture,
        kBottomRadius, 1.0, 1.0, 1.0, false, expected_rayleigh, expected_mie);
    ExpectNear(1.0, (rayleigh / expected_rayleigh)[0](), kEpsilon);
    ExpectNear(1.0, (mie / expected_mie)[0](), kEpsilon);

    // Vertical ray, from top atmosphere boundary, scattering angle 180 degrees.
    rayleigh = GetScattering(
        atmosphere_parameters_, single_rayleigh_scattering_texture,
        kTopRadius, -1.0, 1.0, -1.0, true);
    mie = GetScattering(
        atmosphere_parameters_, single_mie_scattering_texture,
        kTopRadius, -1.0, 1.0, -1.0, true);
    ComputeSingleScattering(
        atmosphere_parameters_, transmittance_texture,
        kTopRadius, -1.0, 1.0, -1.0, true, expected_rayleigh, expected_mie);
    ExpectNear(1.0, (rayleigh / expected_rayleigh)[0](), kEpsilon);
    ExpectNear(1.0, (mie / expected_mie)[0](), kEpsilon);

    // Horizontal ray, from bottom of atmosphere, scattering angle 90 degrees.
    rayleigh = GetScattering(
        atmosphere_parameters_, single_rayleigh_scattering_texture,
        kBottomRadius, 0.0, 0.0, 0.0, false);
    mie = GetScattering(
        atmosphere_parameters_, single_mie_scattering_texture,
        kBottomRadius, 0.0, 0.0, 0.0, false);
    ComputeSingleScattering(
        atmosphere_parameters_, transmittance_texture,
        kBottomRadius, 0.0, 0.0, 0.0, false, expected_rayleigh, expected_mie);
    // The relative error is quite large in this case, i.e. between 6 to 8%.
    ExpectNear(1.0, (rayleigh / expected_rayleigh)[0](), 1e-1);
    ExpectNear(1.0, (mie / expected_mie)[0](), 1e-1);

    // Ray just above the horizon, sun at the zenith.
    Number mu = CosineOfHorizonZenithAngle(kTopRadius);
    rayleigh = GetScattering(
        atmosphere_parameters_, single_rayleigh_scattering_texture,
        kTopRadius, mu, 1.0, mu, false);
    mie = GetScattering(
        atmosphere_parameters_, single_mie_scattering_texture,
        kTopRadius, mu, 1.0, mu, false);
    ComputeSingleScattering(
        atmosphere_parameters_, transmittance_texture,
        kTopRadius, mu, 1.0, mu, false, expected_rayleigh, expected_mie);
    ExpectNear(1.0, (rayleigh / expected_rayleigh)[0](), kEpsilon);
    ExpectNear(1.0, (mie / expected_mie)[0](), kEpsilon);
  }

/*

Multiple scattering, step 1: check that the numerical integration in
ComputeScatteringDensity gives the expected result in two cases
where the integral can be computed analytically:

if the incident radiance from the $(n-1)$-th order is the same in all
directions, and if the ground albedo is $0$. In this case the scattered radiance
is the same in all directions, and equal to the scattering coefficient times the
incident radiance (the effect of the phase functions cancels out because we
integrate over all directions, and the integral of a phase function over all
directions is 1).

if the incident radiance from the $(n-1)$-th order is null, the ground
irradiance is the same everywhere, and the transmittance is 1. Then, at the
ground level, the radiance scattered in a horizontal direction is the scattering
coefficient times the ground irradiance, multiplied by the ground albedo,
divided by $\pi$ (the Lambertian BRDF), and divided by 2 (because the incident
radiance covers only the bottom hemisphere, and because of the symetries of the
phase functions and of the viewing conditions - especially the choice of a
horizontal direction for the scattered radiance).

*/

  void TestComputeScatteringDensity() {
    RadianceSpectrum kRadiance(13.0 * watt_per_square_meter_per_sr_per_nm);
    TransmittanceTexture full_transmittance(DimensionlessSpectrum(1.0));
    ReducedScatteringTexture no_single_scattering(
        IrradianceSpectrum(0.0 * watt_per_square_meter_per_nm));
    ScatteringTexture uniform_multiple_scattering(kRadiance);
    IrradianceTexture no_irradiance(
        IrradianceSpectrum(0.0 * watt_per_square_meter_per_nm));

    RadianceDensitySpectrum scattering_density = ComputeScatteringDensity(
        atmosphere_parameters_, full_transmittance,  no_single_scattering,
        no_single_scattering, uniform_multiple_scattering, no_irradiance,
        kBottomRadius, 0.0, 0.0, 1.0, 3);
    SpectralRadianceDensity kExpectedScatteringDensity =
        (kRayleighScattering + kMieScattering) * kRadiance[0];
    ExpectNear(
        1.0,
        (scattering_density[0] / kExpectedScatteringDensity)(),
        2.0 * kEpsilon);

    IrradianceSpectrum kIrradiance(13.0 * watt_per_square_meter_per_nm);
    IrradianceTexture uniform_irradiance(kIrradiance);
    ScatteringTexture no_multiple_scattering(
        RadianceSpectrum(0.0 * watt_per_square_meter_per_sr_per_nm));
    scattering_density = ComputeScatteringDensity(
        atmosphere_parameters_, full_transmittance, no_single_scattering,
        no_single_scattering, no_multiple_scattering, uniform_irradiance,
        kBottomRadius, 0.0, 0.0, 1.0, 3);
    kExpectedScatteringDensity = (kRayleighScattering + kMieScattering) *
        kGroundAlbedo / (2.0 * PI * sr) * kIrradiance[0];
    ExpectNear(
        1.0,
        (scattering_density[0] / kExpectedScatteringDensity)(),
        2.0 * kEpsilon);
  }

/*

Multiple scattering, step 2: check that the numerical integration in
ComputeMultipleScattering gives the expected result in some cases
where the integral can be computed analytically. If the radiance computed from
step 1 is the same everywhere and in all directions, and if the transmittance
is 1, then the numerical integration in ComputeMultipleScattering
should simply be equal to the radiance times the distance to the nearest
atmosphere boundary. We check this below for a vertical ray looking bottom, and
for a ray looking at the horizon.
*/

  void TestComputeMultipleScattering() {
    RadianceDensitySpectrum kRadianceDensity(
        0.17 * watt_per_cubic_meter_per_sr_per_nm);
    TransmittanceTexture full_transmittance(DimensionlessSpectrum(1.0));
    ScatteringDensityTexture uniform_scattering_density(kRadianceDensity);

    // Vertical ray, looking bottom.
    Length r = kBottomRadius * 0.2 + kTopRadius * 0.8;
    Length distance_to_ground = r - kBottomRadius;
    ExpectNear(
        kRadianceDensity[0] * distance_to_ground,
        ComputeMultipleScattering(atmosphere_parameters_, full_transmittance,
            uniform_scattering_density, r, -1.0, 1.0, -1.0, true)[0],
        kRadianceDensity[0] * distance_to_ground * kEpsilon);

    // Ray just below the horizon.
    Number mu = CosineOfHorizonZenithAngle(kTopRadius);
    Length distance_to_horizon =
        sqrt(kTopRadius * kTopRadius - kBottomRadius * kBottomRadius);
    ExpectNear(
        kRadianceDensity[0] * distance_to_horizon,
        ComputeMultipleScattering(atmosphere_parameters_, full_transmittance,
            uniform_scattering_density, kTopRadius, mu, 1.0, mu, true)[0],
        kRadianceDensity[0] * distance_to_horizon * kEpsilon);
  }

/*

Multiple scattering texture, step 1: check that we get the same result
for the first step of the multiple scattering computation, whether we compute
it directly, or via a quadrilinearly interpolated lookup in a precomputed
texture. For this, we use the same test cases as in
TestComputeScatteringDensity, where the end result can be computed
analytically.
*/

  void TestComputeAndGetScatteringDensity() {
    RadianceSpectrum kRadiance(13.0 * watt_per_square_meter_per_sr_per_nm);
    TransmittanceTexture full_transmittance(DimensionlessSpectrum(1.0));
    ReducedScatteringTexture no_single_scattering(
        IrradianceSpectrum(0.0 * watt_per_square_meter_per_nm));
    ScatteringTexture uniform_multiple_scattering(kRadiance);
    IrradianceTexture no_irradiance(
        IrradianceSpectrum(0.0 * watt_per_square_meter_per_nm));
    LazyScatteringDensityTexture multiple_scattering1(atmosphere_parameters_,
        full_transmittance, no_single_scattering, no_single_scattering,
        uniform_multiple_scattering, no_irradiance, 3);

    RadianceDensitySpectrum scattering_density = GetScattering(
        atmosphere_parameters_, multiple_scattering1,
        kBottomRadius, 0.0, 0.0, 1.0, false);
    SpectralRadianceDensity kExpectedScatteringDensity =
        (kRayleighScattering + kMieScattering) * kRadiance[0];
    ExpectNear(
        1.0,
        (scattering_density[0] / kExpectedScatteringDensity)(),
        2.0 * kEpsilon);

    IrradianceSpectrum kIrradiance(13.0 * watt_per_square_meter_per_nm);
    IrradianceTexture uniform_irradiance(kIrradiance);
    ScatteringTexture no_multiple_scattering(
        RadianceSpectrum(0.0 * watt_per_square_meter_per_sr_per_nm));

    LazyScatteringDensityTexture multiple_scattering2(atmosphere_parameters_,
        full_transmittance, no_single_scattering, no_single_scattering,
        no_multiple_scattering, uniform_irradiance, 3);
    scattering_density = GetScattering(
        atmosphere_parameters_, multiple_scattering2,
        kBottomRadius, 0.0, 0.0, 1.0, false);
    kExpectedScatteringDensity = (kRayleighScattering + kMieScattering) *
        kGroundAlbedo / (2.0 * PI * sr) * kIrradiance[0];
    ExpectNear(
        1.0,
        (scattering_density[0] / kExpectedScatteringDensity)(),
        2.0 * kEpsilon);
  }

/*

Multiple scattering texture, step 2: check that we get the same result
for the second step of the multiple scattering computation, whether we compute
it directly, or via a quadrilinearly interpolated lookup in a precomputed
texture. For this, we use the same test cases as in
TestComputeMultipleScattering, where the end result can be computed
analytically.
*/

  void TestComputeAndGetMultipleScattering() {
    RadianceDensitySpectrum kRadianceDensity(
        0.17 * watt_per_cubic_meter_per_sr_per_nm);
    TransmittanceTexture full_transmittance(DimensionlessSpectrum(1.0));
    ScatteringDensityTexture uniform_scattering_density(kRadianceDensity);
    LazyMultipleScatteringTexture multiple_scattering(atmosphere_parameters_,
        full_transmittance, uniform_scattering_density);

    // Vertical ray, looking bottom.
    Length r = kBottomRadius * 0.2 + kTopRadius * 0.8;
    Length distance_to_ground = r - kBottomRadius;
    ExpectNear(
        kRadianceDensity[0] * distance_to_ground,
        GetScattering(atmosphere_parameters_, multiple_scattering,
            r, -1.0, 1.0, -1.0, true)[0],
        kRadianceDensity[0] * distance_to_ground * kEpsilon);

    // Ray just below the horizon.
    Number mu = CosineOfHorizonZenithAngle(kTopRadius);
    Length distance_to_horizon =
        sqrt(kTopRadius * kTopRadius - kBottomRadius * kBottomRadius);
    ExpectNear(
        kRadianceDensity[0] * distance_to_horizon,
        GetScattering(atmosphere_parameters_, multiple_scattering,
            kTopRadius, mu, 1.0, mu, true)[0],
        kRadianceDensity[0] * distance_to_horizon * kEpsilon);
  }

/*

Ground irradiance, indirect case: check that the numerical integration
in ComputeIndirectIrradiance gives the expected result in a case
where this result can be computed analytically. More precisely, if the sky
radiance is the same everywhere and in all directions, then the ground
irradiance should be equal to $\pi$ times the sky radiance (because
$\int_0^{2\pi}\int_0^{\pi/2}\cos\theta\sin\theta\mathrm{d}\theta\mathrm{d}\phi=
\pi$, which is also why the Lambertian BRDF is $1/\pi$).
*/

  void TestComputeIndirectIrradiance() {
    ReducedScatteringTexture no_single_scattering;
    ScatteringTexture uniform_multiple_scattering(
        RadianceSpectrum(1.0 * watt_per_square_meter_per_sr_per_nm));
    IrradianceSpectrum irradiance = ComputeIndirectIrradiance(
        atmosphere_parameters_, no_single_scattering, no_single_scattering,
        uniform_multiple_scattering, kBottomRadius, 1.0, 2);
    // The relative error is about 1% here.
    ExpectNear(
        PI,
        irradiance[0].to(watt_per_square_meter_per_nm),
        10.0 * kEpsilon);
  }

/*

Mapping to ground irradiance texture coordinates: check that the
boundary values of $r$ ($r_\mathrm{bottom}$ and $r_\mathrm{top}$) and $\mu$
($-1$ and $1$) are mapped to the centers of the boundary texels of the ground
irradiance texture.
*/

  void TestGetIrradianceTextureUvFromRMuS() {
    ExpectNear(
        0.5 / IRRADIANCE_TEXTURE_HEIGHT,
        GetIrradianceTextureUvFromRMuS(
            atmosphere_parameters_, kBottomRadius, 0.0).y(),
        kEpsilon);
    ExpectNear(
        1.0 - 0.5 / IRRADIANCE_TEXTURE_HEIGHT,
        GetIrradianceTextureUvFromRMuS(
            atmosphere_parameters_, kTopRadius, 0.0).y(),
        kEpsilon);
    ExpectNear(
        0.5 / IRRADIANCE_TEXTURE_WIDTH,
        GetIrradianceTextureUvFromRMuS(
            atmosphere_parameters_, kBottomRadius, -1.0).x(),
        kEpsilon);
    ExpectNear(
        1.0 - 0.5 / IRRADIANCE_TEXTURE_WIDTH,
        GetIrradianceTextureUvFromRMuS(
            atmosphere_parameters_, kBottomRadius, 1.0).x(),
        kEpsilon);
  }

/*

Mapping from ground irradiance texture coordinates: check that the
centers of the boundary texels of the ground irradiance texture are mapped to
the boundary values of $r$ ($r_\mathrm{bottom}$ and $r_\mathrm{top}$) and $\mu$
($-1$ and $1$).
*/

  void TestGetRMuSFromIrradianceTextureUv() {
    Length r;
    Number mu_s;
    GetRMuSFromIrradianceTextureUv(atmosphere_parameters_,
        vec2(0.5 / IRRADIANCE_TEXTURE_WIDTH,
             0.5 / IRRADIANCE_TEXTURE_HEIGHT),
        r, mu_s);
    ExpectNear(kBottomRadius, r, 1.0 * m);
    GetRMuSFromIrradianceTextureUv(atmosphere_parameters_,
        vec2(0.5 / IRRADIANCE_TEXTURE_WIDTH,
             1.0 - 0.5 / IRRADIANCE_TEXTURE_HEIGHT),
        r, mu_s);
    ExpectNear(kTopRadius, r, 1.0 * m);
    GetRMuSFromIrradianceTextureUv(atmosphere_parameters_,
        vec2(0.5 / IRRADIANCE_TEXTURE_WIDTH,
             0.5 / IRRADIANCE_TEXTURE_HEIGHT),
        r, mu_s);
    ExpectNear(-1.0, mu_s(), kEpsilon);
    GetRMuSFromIrradianceTextureUv(atmosphere_parameters_,
        vec2(1.0 - 0.5 / IRRADIANCE_TEXTURE_WIDTH,
             0.5 / IRRADIANCE_TEXTURE_HEIGHT),
        r, mu_s);
    ExpectNear(1.0, mu_s(), kEpsilon);
  }

/*

Ground irradiance texture: check that we get the same ground
irradiance whether we compute it directly with
ComputeIndirectIrradiance, or via a bilinearly interpolated lookup
in the precomputed ground irradiance texture.
*/

  void TestComputeAndGetIrradiance() {
    ReducedScatteringTexture no_single_scattering(
        IrradianceSpectrum(0.0 * watt_per_square_meter_per_nm));
    ScatteringTexture fake_multiple_scattering;
    for (unsigned int x = 0; x < fake_multiple_scattering.size_x(); ++x) {
      for (unsigned int y = 0; y < fake_multiple_scattering.size_y(); ++y) {
        for (unsigned int z = 0; z < fake_multiple_scattering.size_z(); ++z) {
          double v = z + fake_multiple_scattering.size_z() *
              (y + fake_multiple_scattering.size_y() * x);
          fake_multiple_scattering.Set(x, y, z,
              RadianceSpectrum(v * watt_per_square_meter_per_sr_per_nm));
        }
      }
    }

    Length r = kBottomRadius * 0.8 + kTopRadius * 0.2;
    Number mu_s = 0.25;
    int scattering_order = 2;
    LazyIndirectIrradianceTexture irradiance_texture(atmosphere_parameters_,
        no_single_scattering, no_single_scattering, fake_multiple_scattering,
        scattering_order);
    ExpectNear(
        1.0,
        (GetIrradiance(atmosphere_parameters_, irradiance_texture, r, mu_s) /
            ComputeIndirectIrradiance(atmosphere_parameters_,
                no_single_scattering, no_single_scattering,
                fake_multiple_scattering, r, mu_s, scattering_order))[0](),
        kEpsilon);
    ExpectNotNear(
        1.0,
        (GetIrradiance(atmosphere_parameters_, irradiance_texture, r, mu_s) /
            ComputeIndirectIrradiance(atmosphere_parameters_,
                no_single_scattering, no_single_scattering,
                fake_multiple_scattering, r, 0.5, scattering_order))[0](),
        kEpsilon);
  }

/*

And that's it for the unit tests! We just need to implement the two methods
that we used above to set a uniform density of air molecules and aerosols, and
to remove aerosols:
*/

 private:
  void SetUniformAtmosphere() {
    atmosphere_parameters_.rayleigh_density.layers[0] = DensityProfileLayer();
    atmosphere_parameters_.rayleigh_density.layers[1] =
        DensityProfileLayer(0.0 * m, 0.0, 0.0 / m, 0.0 / m, 1.0);
    atmosphere_parameters_.mie_density.layers[0] = DensityProfileLayer();
    atmosphere_parameters_.mie_density.layers[1] =
        DensityProfileLayer(0.0 * m, 0.0, 0.0 / m, 0.0 / m, 1.0);
    atmosphere_parameters_.absorption_density.layers[0] = DensityProfileLayer();
    atmosphere_parameters_.absorption_density.layers[1] = DensityProfileLayer();
  }

  void RemoveAerosols() {
    atmosphere_parameters_.mie_scattering[0] = 0.0 / km;
    atmosphere_parameters_.mie_extinction[0] = 0.0 / km;
  }

  AtmosphereParameters atmosphere_parameters_;
};

/*

Finally, we need to create an instance of each of the above test cases (which
has the side effect of registering these instances in the TestCase
class, which can then run all the tests in its RunAllTests static
method).
*/

namespace {

FunctionsTest distance_to_top_atmosphere_boundary(
    "DistanceToTopAtmosphereBoundary",
    &FunctionsTest::TestDistanceToTopAtmosphereBoundary);
FunctionsTest ray_intersects_ground(
    "RayIntersectsGround",
    &FunctionsTest::TestRayIntersectsGround);
FunctionsTest get_profile_density(
    "GetProfileDensity",
    &FunctionsTest::TestGetProfileDensity);
FunctionsTest compute_optical_length_to_top_atmosphere_boundary(
    "ComputeOpticalLengthToTopAtmosphereBoundary",
    &FunctionsTest::TestComputeOpticalLengthToTopAtmosphereBoundary);
FunctionsTest compute_transmittance_to_top_atmosphere_boundary(
    "ComputeTransmittanceToTopAtmosphereBoundary",
    &FunctionsTest::TestComputeTransmittanceToTopAtmosphereBoundary);
FunctionsTest get_texture_coord_from_unit_range(
    "GetTextureCoordFromUnitRange",
    &FunctionsTest::TestGetTextureCoordFromUnitRange);
FunctionsTest get_transmittance_texture_uv_from_rmu(
    "GetTransmittanceTextureUvFromRMu",
    &FunctionsTest::TestGetTransmittanceTextureUvFromRMu);
FunctionsTest get_rmu_from_transmittance_texture_uv(
    "GetRMuFromTransmittanceTextureUv",
    &FunctionsTest::TestGetRMuFromTransmittanceTextureUv);
FunctionsTest get_transmittance_to_top_atmosphere_boundary(
    "GetTransmittanceToTopAtmosphereBoundary",
    &FunctionsTest::TestGetTransmittanceToTopAtmosphereBoundary);
FunctionsTest compute_and_get_transmittance(
    "ComputeAndGetTransmittance",
    &FunctionsTest::TestComputeAndGetTransmittance);

FunctionsTest compute_single_scattering_integrand(
    "ComputeSingleScatteringIntegrand",
    &FunctionsTest::TestComputeSingleScatteringIntegrand);
FunctionsTest distance_to_nearest_atmosphere_boundary(
    "DistanceToNearestAtmosphereBoundary",
    &FunctionsTest::TestDistanceToNearestAtmosphereBoundary);
FunctionsTest compute_single_scattering(
    "ComputeSingleScattering",
    &FunctionsTest::TestComputeSingleScattering);
FunctionsTest phase_functions(
    "PhaseFunctions",
    &FunctionsTest::TestPhaseFunctions);
FunctionsTest get_scattering_texture_uvwz_from_rmumusnu(
    "GetScatteringTextureUvwzFromRMuMuSNu",
    &FunctionsTest::TestGetScatteringTextureUvwzFromRMuMuSNu);
FunctionsTest get_rmumusnu_from_scattering_texture_uvwz(
    "GetRMuMuSNuFromScatteringTextureUvwz",
    &FunctionsTest::TestGetRMuMuSNuFromScatteringTextureUvwz);
FunctionsTest compute_and_get_scattering(
    "ComputeAndGetSingleScattering",
    &FunctionsTest::TestComputeAndGetSingleScattering);

FunctionsTest compute_scattering_density(
    "ComputeScatteringDensity",
    &FunctionsTest::TestComputeScatteringDensity);
FunctionsTest compute_multiple_scattering(
    "ComputeMultipleScattering",
    &FunctionsTest::TestComputeMultipleScattering);
FunctionsTest compute_and_get_scattering_density(
    "ComputeAndGetScatteringDensity",
    &FunctionsTest::TestComputeAndGetScatteringDensity);
FunctionsTest compute_and_get_multiple_scattering(
    "ComputeAndGetMultipleScattering",
    &FunctionsTest::TestComputeAndGetMultipleScattering);

FunctionsTest compute_indirect_irradiance(
    "ComputeIndirectIrradiance",
    &FunctionsTest::TestComputeIndirectIrradiance);
FunctionsTest get_irradiance_texture_uv_from_rmus(
    "GetIrradianceTextureUvFromRMuS",
    &FunctionsTest::TestGetIrradianceTextureUvFromRMuS);
FunctionsTest get_rmus_from_irradiance_texture_uv(
    "GetRMuSFromIrradianceTextureUv",
    &FunctionsTest::TestGetRMuSFromIrradianceTextureUv);
FunctionsTest get_irradiance(
    "GetComputeAndGetIrradiance",
    &FunctionsTest::TestComputeAndGetIrradiance);

}  // anonymous namespace

}  // namespace reference
}  // namespace atmosphere