Skip to content

Commit

Permalink
Adds RunPlanVector::setPropertyStep()
Browse files Browse the repository at this point in the history
This has behaviour similar to Python's range(), more appropriate than lerp in many cases with integers.

Providing this will hopefully help people avoid using lerp wrongly for integers.
  • Loading branch information
Robadob committed Nov 23, 2023
1 parent 1561815 commit 8c3dd88
Show file tree
Hide file tree
Showing 4 changed files with 240 additions and 6 deletions.
90 changes: 87 additions & 3 deletions include/flamegpu/simulation/RunPlanVector.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,11 @@ class RunPlanVector : private std::vector<RunPlan> {
* @throws exception::InvalidEnvProperty If a property of the name does not exist
* @throws exception::InvalidEnvPropertyType If a property with the name has a type different to T, or length > 1
* @throws exception::OutOfBoundsException If this vector has a length less than 2
* @see setPropertyLerpRange(const std::string &name, flamegpu::size_type, T min, T max)
* @see setPropertyStep(const std::string&, T, T)
*/
template<typename T>
void setPropertyLerpRange(const std::string &name, const T min, const T max);
void setPropertyLerpRange(const std::string &name, T min, T max);
/**
* Array property element equivalent of setPropertyLerpRange()
* Sweep element of named environment property array over an inclusive uniform distribution
Expand All @@ -130,10 +132,43 @@ class RunPlanVector : private std::vector<RunPlan> {
* @throws exception::InvalidEnvPropertyType If a property with the name has a type different to T
* @throws exception::OutOfBoundsException If index is greater than or equal to the length of the environment property array
* @throws exception::OutOfBoundsException If this vector has a length less than 2
* @see setPropertyUniformDistribution(const std::string &name, T min, T max)
* @see setPropertyLerpRange(const std::string &name, T min, T max)
* @see setPropertyStep(const std::string&, flamegpu::size_type, T, T)
*/
template<typename T>
void setPropertyLerpRange(const std::string &name, const flamegpu::size_type index, const T min, const T max);
void setPropertyLerpRange(const std::string &name, flamegpu::size_type index, T min, T max);
/**
* Increment named environment property with a user defined step
* value = init + index * step
* @param name The name of the environment property to set
* @param init The value of the first environment property
* @param step The value to increment each subsequent environment property by
* @tparam T The type of the environment property, this must match the ModelDescription
* @throws exception::InvalidEnvProperty If a property of the name does not exist
* @throws exception::InvalidEnvPropertyType If a property with the name has a type different to T, or length > 1
* @throws exception::OutOfBoundsException If this vector has a length less than 2
* @see setPropertyStep(const std::string&, flamegpu::size_type, T, T)
* @see setPropertyLerpRange(const std::string&, T, T)
*/
template<typename T>
void setPropertyStep(const std::string& name, T init, T step);
/**
* Array property element equivalent of setPropertyStep()
* Increment named environment property with a user defined step
* value = init + index * step
* @param name The name of the environment property to set
* @param index The index of the element within the environment property array to set
* @param init The value of the first environment property
* @param step The value to increment each subsequent environment property by
* @tparam T The type of the environment property, this must match the ModelDescription
* @throws exception::InvalidEnvProperty If a property of the name does not exist
* @throws exception::InvalidEnvPropertyType If a property with the name has a type different to T, or length > 1
* @throws exception::OutOfBoundsException If this vector has a length less than 2
* @see setPropertyStep(const std::string&, T, T)
* @see setPropertyLerpRange(const std::string&, flamegpu::size_type, T, T)
*/
template<typename T>
void setPropertyStep(const std::string& name, flamegpu::size_type index, T init, T step);
/**
* Seed the internal random generator used for random property distributions
* This will only affect subsequent calls to setPropertyRandom()
Expand Down Expand Up @@ -471,6 +506,55 @@ void RunPlanVector::setPropertyLerpRange(const std::string &name, const flamegpu
}
}

template<typename T>
void RunPlanVector::setPropertyStep(const std::string& name, T init, const T step) {
// Validation
const auto it = environment->find(name);
if (it == environment->end()) {
THROW exception::InvalidEnvProperty("Environment description does not contain property '%s', "
"in RunPlanVector::setPropertyStep()\n",
name.c_str());
}
if (it->second.data.type != std::type_index(typeid(T))) {
THROW exception::InvalidEnvPropertyType("Environment property '%s' type mismatch '%s' != '%s', "
"in RunPlanVector::setPropertyStep()\n",
name.c_str(), it->second.data.type.name(), std::type_index(typeid(T)).name());
}
if (it->second.data.elements != 1) {
THROW exception::InvalidEnvPropertyType("Environment property '%s' is an array with %u elements, array method should be used, "
"in RunPlanVector::setPropertyStep()\n",
name.c_str(), it->second.data.elements);
}
for (auto& i : *this) {
i.setProperty<T>(name, init);
init += step;
}
}
template<typename T>
void RunPlanVector::setPropertyStep(const std::string& name, const flamegpu::size_type index, T init, const T step) {
// Validation
const auto it = environment->find(name);
if (it == environment->end()) {
THROW exception::InvalidEnvProperty("Environment description does not contain property '%s', "
"in RunPlanVector::setPropertyStep()\n",
name.c_str());
}
if (it->second.data.type != std::type_index(typeid(T))) {
THROW exception::InvalidEnvPropertyType("Environment property '%s' type mismatch '%s' != '%s', "
"in RunPlanVector::setPropertyStep()\n",
name.c_str(), it->second.data.type.name(), std::type_index(typeid(T)).name());
}
const unsigned int t_index = detail::type_decode<T>::len_t * index + detail::type_decode<T>::len_t;
if (t_index > it->second.data.elements || t_index < index) {
throw exception::OutOfBoundsException("Environment property array index out of bounds "
"in RunPlanVector::setPropertyStep()\n");
}
for (auto& i : *this) {
i.setProperty<T>(name, index, init);
init += step;
}
}

template<typename T, typename rand_dist>
void RunPlanVector::setPropertyRandom(const std::string &name, rand_dist &distribution) {
// Validation
Expand Down
1 change: 1 addition & 0 deletions swig/python/flamegpu.i
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,7 @@ TEMPLATE_VARIABLE_INSTANTIATE_ID(setProperty, flamegpu::RunPlanVector::setProper
TEMPLATE_VARIABLE_ARRAY_INSTANTIATE_ID(setProperty, flamegpu::RunPlanVector::setProperty)
TEMPLATE_VARIABLE_INSTANTIATE_ID(setPropertyArray, flamegpu::RunPlanVector::setPropertyArray)
TEMPLATE_VARIABLE_INSTANTIATE(setPropertyLerpRange, flamegpu::RunPlanVector::setPropertyLerpRange)
TEMPLATE_VARIABLE_INSTANTIATE(setPropertyStep, flamegpu::RunPlanVector::setPropertyStep)
TEMPLATE_VARIABLE_INSTANTIATE(setPropertyUniformRandom, flamegpu::RunPlanVector::setPropertyUniformRandom)
TEMPLATE_VARIABLE_INSTANTIATE_FLOATS(setPropertyNormalRandom, flamegpu::RunPlanVector::setPropertyNormalRandom)
TEMPLATE_VARIABLE_INSTANTIATE_FLOATS(setPropertyLogNormalRandom, flamegpu::RunPlanVector::setPropertyLogNormalRandom)
Expand Down
78 changes: 76 additions & 2 deletions tests/python/simulation/test_RunPlanVector.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,6 @@ def test_setProperty(self):
assert e.value.type() == "OutOfBoundsException"

# Check that all values set lie within the min and max inclusive
# @todo - should fp be [min, max) like when using RNG?
def test_setPropertyLerpRange(self):
# Define the simple model to use
model = pyflamegpu.ModelDescription("test")
Expand All @@ -194,6 +193,81 @@ def test_setPropertyLerpRange(self):
plans = pyflamegpu.RunPlanVector(model, totalPlans)
# No need to seed the random, as this is a LERP rather than a random distribution.

# Uniformly set each property to a new value, then check it has been applied correctly.
fMin = 1.
fStep = 100.
iMin = 1
iStep = 100
u3Min = (1, 101, 201)
u3Step = (100, 200, 300)
# void setPropertyStep(const std::string &name, const T &init, const T &step)
plans.setPropertyStepFloat("f", fMin, fStep)
plans.setPropertyStepInt("i", iMin, iStep)
# Check setting individual array elements
# void setPropertyStep(const std::string &name, const EnvironmentManager::size_type &index, const T &init, const T &step)
plans.setPropertyStepUInt("u3", 0, u3Min[0], u3Step[0])
plans.setPropertyStepUInt("u3", 1, u3Min[1], u3Step[1])
plans.setPropertyStepUInt("u3", 2, u3Min[2], u3Step[2])
# Check values are as expected by accessing the properties from each plan
i = 0
for plan in plans:
assert plan.getPropertyFloat("f") >= fMin + (i-0.01) * fStep
assert plan.getPropertyFloat("f") <= fMin + (i+0.01) * fStep
assert plan.getPropertyInt("i") == iMin + i * iStep
u3FromPlan = plan.getPropertyArrayUInt("u3")
assert u3FromPlan[0] == u3Min[0] + i * u3Step[0]
assert u3FromPlan[1] == u3Min[1] + i * u3Step[1]
assert u3FromPlan[2] == u3Min[2] + i * u3Step[2]
i += 1


# Tests for exceptions
# --------------------
singlePlanVector = pyflamegpu.RunPlanVector(model, 1)
# Note litereals used must match the templated type not the incorrect types used, to appease MSVC warnings.
# void RunPlanVector::setPropertyStep(const std::string &name, const T &init, const T &step)
with pytest.raises(pyflamegpu.FLAMEGPURuntimeException) as e:
plans.setPropertyStepFloat("does_not_exist", 1., 100.)
assert e.value.type() == "InvalidEnvProperty"
with pytest.raises(pyflamegpu.FLAMEGPURuntimeException) as e:
plans.setPropertyStepFloat("i", 1., 100.)
assert e.value.type() == "InvalidEnvPropertyType"
with pytest.raises(pyflamegpu.FLAMEGPURuntimeException) as e:
plans.setPropertyStepUInt("u3", 1, 100)
assert e.value.type() == "InvalidEnvPropertyType"
# void RunPlanVector::setPropertyStep(const std::string &name, const EnvironmentManager::size_type, const T &init, const T &step)
# Extra brackets within the macro mean commas can be used due to how preproc tokenizers work
with pytest.raises(pyflamegpu.FLAMEGPURuntimeException) as e:
plans.setPropertyStepFloat("does_not_exist", 0, 1., 100.)
assert e.value.type() == "InvalidEnvProperty"
with pytest.raises(pyflamegpu.FLAMEGPURuntimeException) as e:
plans.setPropertyStepFloat("u3", 0, 1., 100.)
assert e.value.type() == "InvalidEnvPropertyType"
with pytest.raises(pyflamegpu.FLAMEGPURuntimeException) as e:
minus_one_uint32_t = -1 & 0xffffffff
plans.setPropertyStepUInt("u3", minus_one_uint32_t, 1, 100)
assert e.value.type() == "OutOfBoundsException"
with pytest.raises(pyflamegpu.FLAMEGPURuntimeException) as e:
plans.setPropertyStepUInt("u3", 4, 1, 100)
assert e.value.type() == "OutOfBoundsException"

# Check that all values match expected
def test_setPropertyStep(self):
# Define the simple model to use
model = pyflamegpu.ModelDescription("test")
# Add a few environment properties to the model.
environment = model.Environment()
fOriginal = 0.0
iOriginal = 0
u3Original = (0, 0, 0)
environment.newPropertyFloat("f", fOriginal)
environment.newPropertyInt("i", iOriginal)
environment.newPropertyArrayUInt("u3", u3Original)
# Create a vector of plans
totalPlans = 10
plans = pyflamegpu.RunPlanVector(model, totalPlans)
# No need to seed the random, as this is a LERP rather than a random distribution.

# Uniformly set each property to a new value, then check it has been applied correctly.
fMin = 1.
fMax = 100.
Expand Down Expand Up @@ -259,7 +333,7 @@ def test_setPropertyLerpRange(self):
with pytest.raises(pyflamegpu.FLAMEGPURuntimeException) as e:
plans.setPropertyLerpRangeUInt("u3", 4, 1, 100)
assert e.value.type() == "OutOfBoundsException"

# Checking for uniformity of distribution would require a very large samples size.
# As std:: is used, we trust the distribution is legit, and instead just check for min/max.
def test_setPropertyUniformRandom(self):
Expand Down
77 changes: 76 additions & 1 deletion tests/test_cases/simulation/test_RunPlanVector.cu
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ double t_lerp(const T _min, const T _max, const double a) {
}

// Check that all values set lie within the min and max inclusive
// @todo - should fp be [min, max) like when using RNG?
TEST(TestRunPlanVector, setPropertyUniformDistribution) {
// Define the simple model to use
flamegpu::ModelDescription model("test");
Expand Down Expand Up @@ -223,6 +222,7 @@ TEST(TestRunPlanVector, setPropertyUniformDistribution) {
plans.setPropertyLerpRange("u3", 2, u3Min[2], u3Max[2]);
plans.setPropertyLerpRange("f2", 0, f2Min[0], f2Max[0]);
plans.setPropertyLerpRange("f2", 1, f2Min[1], f2Max[1]);

// Check values are as expected by accessing the properties from each plan
int i = 0;
const double divisor = totalPlans - 1;
Expand Down Expand Up @@ -258,6 +258,81 @@ TEST(TestRunPlanVector, setPropertyUniformDistribution) {
EXPECT_THROW((plans.setPropertyLerpRange<uint32_t>("u3", static_cast<flamegpu::size_type>(-1), 1u, 100u)), exception::OutOfBoundsException);
EXPECT_THROW((plans.setPropertyLerpRange<uint32_t>("u3", 4u, 1u, 100u)), exception::OutOfBoundsException);
}
// Check that all values set lie within the min and max inclusive
TEST(TestRunPlanVector, setPropertyStep) {
// Define the simple model to use
flamegpu::ModelDescription model("test");
// Add a few environment properties to the model.
auto environment = model.Environment();
const float fOriginal = 0.0f;
const int32_t iOriginal = 0;
const std::array<uint32_t, 3> u3Original = { {0, 0, 0} };
const std::array<float, 2> f2Original = { {12.0f, 13.0f} };
environment.newProperty<float>("f", fOriginal);
environment.newProperty<float>("fb", fOriginal);
environment.newProperty<int32_t>("i", iOriginal);
environment.newProperty<uint32_t, 3>("u3", u3Original);
environment.newProperty<float, 2>("f2", f2Original);
// Create a vector of plans
constexpr uint32_t totalPlans = 10u;
flamegpu::RunPlanVector plans(model, totalPlans);
// No need to seed the random, as does not use a random distribution.

// Uniformly set each property to a new value, then check it has been applied correctly.
const float fMin = 1.f;
const float fStep = 100.f;
const float fbMin = 0.0f;
const float fbStep = 1.0f;
const int32_t iMin = 1;
const int32_t iStep = 100;
const std::array<uint32_t, 3> u3Min = { {1u, 101u, 201u} };
const std::array<uint32_t, 3> u3Step = { {100u, 200u, 300u} };
const std::array<float, 2> f2Min = { {1.0f, 100.f} };
const std::array<float, 2> f2Step = { {0.0f, -100.0f} };
// void setPropertyStep(const std::string &name, T init, T step);
plans.setPropertyStep("f", fMin, fStep);
plans.setPropertyStep("fb", fbMin, fbStep);
plans.setPropertyStep("i", iMin, iStep);
// Check setting individual array elements
// void setPropertyStep(const std::string &name, flamegpu::size_type index, T init, T step);
plans.setPropertyStep("u3", 0, u3Min[0], u3Step[0]);
plans.setPropertyStep("u3", 1, u3Min[1], u3Step[1]);
plans.setPropertyStep("u3", 2, u3Min[2], u3Step[2]);
plans.setPropertyStep("f2", 0, f2Min[0], f2Step[0]);
plans.setPropertyStep("f2", 1, f2Min[1], f2Step[1]);

// Check values are as expected by accessing the properties from each plan
int i = 0;
for (const auto& plan : plans) {
EXPECT_EQ(plan.getProperty<float>("f"), fMin + i * fStep);
EXPECT_EQ(plan.getProperty<float>("fb"), fbMin + i * fbStep);
const std::array<float, 2> f2FromPlan = plan.getProperty<float, 2>("f2");
EXPECT_EQ(f2FromPlan[0], f2Min[0] + i * f2Step[0]);
EXPECT_EQ(f2FromPlan[1], f2Min[1] + i * f2Step[1]);
// Note integer values are rounded
EXPECT_EQ(plan.getProperty<int32_t>("i"), iMin + i * iStep);
const std::array<uint32_t, 3> u3FromPlan = plan.getProperty<uint32_t, 3>("u3");
EXPECT_EQ(u3FromPlan[0], u3Min[0] + i * u3Step[0]);
EXPECT_EQ(u3FromPlan[1], u3Min[1] + i * u3Step[1]);
EXPECT_EQ(u3FromPlan[2], u3Min[2] + i * u3Step[2]);
++i;
}

// Tests for exceptions
// --------------------
flamegpu::RunPlanVector singlePlanVector(model, 1);
// Note literals used must match the templated type not the incorrect types used, to appease MSVC warnings.
// void RunPlanVector::setPropertyStep(const std::string &name, T init, T step)
EXPECT_THROW((plans.setPropertyStep<float>("does_not_exist", 1.f, 100.f)), flamegpu::exception::InvalidEnvProperty);
EXPECT_THROW((plans.setPropertyStep<float>("i", 1.f, 100.f)), flamegpu::exception::InvalidEnvPropertyType);
EXPECT_THROW((plans.setPropertyStep<uint32_t>("u3", 1u, 100u)), flamegpu::exception::InvalidEnvPropertyType);
// void RunPlanVector::setPropertyStep(const std::string &name, const flamegpu::size_type, T init, T step)
// Extra brackets within the macro mean commas can be used due to how preproc tokenizers work
EXPECT_THROW((plans.setPropertyStep<float>("does_not_exist", 0u, 1.f, 100.f)), flamegpu::exception::InvalidEnvProperty);
EXPECT_THROW((plans.setPropertyStep<float>("u3", 0u, 1.f, 100.f)), flamegpu::exception::InvalidEnvPropertyType);
EXPECT_THROW((plans.setPropertyStep<uint32_t>("u3", static_cast<flamegpu::size_type>(-1), 1u, 100u)), exception::OutOfBoundsException);
EXPECT_THROW((plans.setPropertyStep<uint32_t>("u3", 4u, 1u, 100u)), exception::OutOfBoundsException);
}
// Checking for uniformity of distribution would require a very large samples size.
// As std:: is used, we trust the distribution is legit, and instead just check for min/max.
TEST(TestRunPlanVector, setPropertyUniformRandom) {
Expand Down

0 comments on commit 8c3dd88

Please sign in to comment.