nori/src/ttest.cpp

224 lines
8.8 KiB
C++

/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <nori/scene.h>
#include <nori/bsdf.h>
#include <nori/camera.h>
#include <nori/integrator.h>
#include <nori/sampler.h>
#include <hypothesis.h>
#include <pcg32.h>
/*
* =======================================================================
* WARNING WARNING WARNING WARNING WARNING WARNING
* =======================================================================
* Remember to put on SAFETY GOGGLES before looking at this file. You
* are most certainly not expected to read or understand any of it.
* =======================================================================
*/
NORI_NAMESPACE_BEGIN
/**
* Student's t-test for the equality of means
*
* This test analyzes whether the expected value of a random variable matches a
* certain known value. When there is significant statistical "evidence"
* against this hypothesis, the test fails.
*
* This is useful in checking whether a Monte Carlo method method converges
* against the right value. Because statistical tests are able to handle the
* inherent noise of these methods, they can be used to construct statistical
* test suites not unlike the traditional unit tests used in software engineering.
*
* This implementation can be used to test two things:
*
* 1. that the illumination scattered by a BRDF model under uniform illumination
* into a certain direction matches a given value (modulo noise).
*
* 2. that the average radiance received by a camera within some scene
* matches a given value (modulo noise).
*/
class StudentsTTest : public NoriObject {
public:
StudentsTTest(const PropertyList &propList) {
/* The null hypothesis will be rejected when the associated
p-value is below the significance level specified here. */
m_significanceLevel = propList.getFloat("significanceLevel", 0.01f);
/* This parameter specifies a list of incidence angles that will be tested */
std::vector<std::string> angles = tokenize(propList.getString("angles", ""));
for (auto angle : angles)
m_angles.push_back(toFloat(angle));
/* This parameter specifies a list of reference values, one for each angle */
std::vector<std::string> references = tokenize(propList.getString("references", ""));
for (auto angle : references)
m_references.push_back(toFloat(angle));
/* Number of BSDF samples that should be generated (default: 100K) */
m_sampleCount = propList.getInteger("sampleCount", 100000);
}
virtual ~StudentsTTest() {
for (auto bsdf : m_bsdfs)
delete bsdf;
for (auto scene : m_scenes)
delete scene;
}
void addChild(NoriObject *obj) {
switch (obj->getClassType()) {
case EBSDF:
m_bsdfs.push_back(static_cast<BSDF *>(obj));
break;
case EScene:
m_scenes.push_back(static_cast<Scene *>(obj));
break;
default:
throw NoriException("StudentsTTest::addChild(<%s>) is not supported!",
classTypeName(obj->getClassType()));
}
}
/// Invoke a series of t-tests on the provided input
void activate() {
int total = 0, passed = 0;
pcg32 random;
if (!m_bsdfs.empty()) {
if (m_references.size() * m_bsdfs.size() != m_angles.size())
throw NoriException("Specified a different number of angles and reference values!");
if (!m_scenes.empty())
throw NoriException("Cannot test BSDFs and scenes at the same time!");
/* Test each registered BSDF */
int ctr = 0;
for (auto bsdf : m_bsdfs) {
for (size_t i=0; i<m_references.size(); ++i) {
float angle = m_angles[i], reference = m_references[ctr++];
cout << "------------------------------------------------------" << endl;
cout << "Testing (angle=" << angle << "): " << bsdf->toString() << endl;
++total;
BSDFQueryRecord bRec(sphericalDirection(degToRad(angle), 0));
cout << "Drawing " << m_sampleCount << " samples .. " << endl;
double mean=0, variance = 0;
for (int k=0; k<m_sampleCount; ++k) {
Point2f sample(random.nextFloat(), random.nextFloat());
double result = (double) bsdf->sample(bRec, sample).getLuminance();
/* Numerically robust online variance estimation using an
algorithm proposed by Donald Knuth (TAOCP vol.2, 3rd ed., p.232) */
double delta = result - mean;
mean += delta / (double) (k+1);
variance += delta * (result - mean);
}
variance /= m_sampleCount - 1;
std::pair<bool, std::string>
result = hypothesis::students_t_test(mean, variance, reference,
m_sampleCount, m_significanceLevel, (int) m_references.size());
if (result.first)
++passed;
cout << result.second << endl;
}
}
} else {
if (m_references.size() != m_scenes.size())
throw NoriException("Specified a different number of scenes and reference values!");
Sampler *sampler = static_cast<Sampler *>(
NoriObjectFactory::createInstance("independent", PropertyList()));
int ctr = 0;
for (auto scene : m_scenes) {
Integrator *integrator = scene->getIntegrator();
integrator->preprocess(scene);
const Camera *camera = scene->getCamera();
float reference = m_references[ctr++];
cout << "------------------------------------------------------" << endl;
cout << "Testing scene: " << scene->toString() << endl;
++total;
cout << "Generating " << m_sampleCount << " paths.. " << endl;
double mean = 0, variance = 0;
for (int k=0; k<m_sampleCount; ++k) {
/* Sample a ray from the camera */
Ray3f ray;
Point2f pixelSample = (sampler->next2D().array()
* camera->getOutputSize().cast<float>().array()).matrix();
Color3f value = camera->sampleRay(ray, pixelSample, sampler->next2D());
/* Compute the incident radiance */
value *= integrator->Li(scene, sampler, ray);
/* Numerically robust online variance estimation using an
algorithm proposed by Donald Knuth (TAOCP vol.2, 3rd ed., p.232) */
double result = (double) value.getLuminance();
double delta = result - mean;
mean += delta / (double) (k+1);
variance += delta * (result - mean);
}
variance /= m_sampleCount - 1;
std::pair<bool, std::string>
result = hypothesis::students_t_test(mean, variance, reference,
m_sampleCount, m_significanceLevel, (int) m_references.size());
if (result.first)
++passed;
cout << result.second << endl;
}
}
cout << "Passed " << passed << "/" << total << " tests." << endl;
if (passed < total)
throw std::runtime_error("Some tests failed :(");
}
std::string toString() const {
return tfm::format(
"StudentsTTest[\n"
" significanceLevel = %f,\n"
" sampleCount= %i\n"
"]",
m_significanceLevel,
m_sampleCount
);
}
EClassType getClassType() const { return ETest; }
private:
std::vector<BSDF *> m_bsdfs;
std::vector<Scene *> m_scenes;
std::vector<float> m_angles;
std::vector<float> m_references;
float m_significanceLevel;
int m_sampleCount;
};
NORI_REGISTER_CLASS(StudentsTTest, "ttest");
NORI_NAMESPACE_END