Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sector and income #230

Merged
merged 29 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
de623fd
Add SectorPrevalence to KevinHall.json config.
jamesturner246 Oct 20, 2023
d638cef
Load Rural Prevalence into model parser from JSON.
jamesturner246 Oct 20, 2023
585e936
Load Rural Prevalence into KH model.
jamesturner246 Oct 20, 2023
1922906
We want all our RF models' generate_ methods to run.
jamesturner246 Oct 20, 2023
3a6534e
Add stub initialise_sector method to KH model class.
jamesturner246 Oct 20, 2023
e5a312c
Add enum type for sector (region).
jamesturner246 Oct 20, 2023
87c4eac
Add 'unknown' sector enum type, and inititalise people to unknown sec…
jamesturner246 Oct 20, 2023
5f9a7fe
Finish initialise_sector code in KevinHall.
jamesturner246 Oct 20, 2023
eb35572
Clarify that we are using rural prevalence here.
jamesturner246 Oct 20, 2023
c404c0a
Don't std move trivial type
jamesturner246 Oct 20, 2023
f4d6638
model parser clang-tidy.
jamesturner246 Oct 23, 2023
b4d72ab
Add Income model parameters to KevinHall config JSON.
jamesturner246 Oct 23, 2023
d7f5b61
Load income models from JSON in mdoel parser.
jamesturner246 Oct 23, 2023
7c0846f
Load income models from mdoel parser into kevin hall model.
jamesturner246 Oct 23, 2023
1aea169
Add missing category_1 income model.
jamesturner246 Oct 24, 2023
3c6ad90
LinearModelParams should be a vector.
jamesturner246 Oct 24, 2023
c72c2bc
Add initialise_income to kevin hall model.
jamesturner246 Oct 24, 2023
61ab5b7
Fix read after std::move.
jamesturner246 Oct 25, 2023
06e553e
Call initialise_income in KH model.
jamesturner246 Oct 25, 2023
e373e53
Add sector update code to kevinhall.
jamesturner246 Oct 25, 2023
f5a22d2
Code to convert sector to a real, for use as a coefficient. Add error…
jamesturner246 Oct 25, 2023
2326ca9
Age group name consistency.
jamesturner246 Oct 25, 2023
3985efa
Add person method to check over 18.
jamesturner246 Oct 25, 2023
905223a
Add kevinhall code to update income.
jamesturner246 Oct 25, 2023
71926bd
Change weird if
jamesturner246 Oct 26, 2023
d32fd44
nutrient_ranges is now a Double interval, interval now has its own cl…
jamesturner246 Oct 26, 2023
a44f19c
KH: rural_prevalence should be an unordered_map.
jamesturner246 Oct 26, 2023
0ab8ef6
KH: use vector reserve, rather than letting it zero init.
jamesturner246 Oct 26, 2023
1ebaa1a
KH: push_back, don't assign, into reserved vector.
jamesturner246 Oct 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions src/HealthGPS.Console/model_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -275,14 +275,10 @@ load_kevinhall_risk_model_definition(const poco::json &opt, const host::Configur

// Nutrient groups.
std::unordered_map<hgps::core::Identifier, double> energy_equation;
std::unordered_map<hgps::core::Identifier, std::pair<double, double>> nutrient_ranges;
std::unordered_map<hgps::core::Identifier, hgps::core::DoubleInterval> nutrient_ranges;
for (const auto &nutrient : opt["Nutrients"]) {
auto nutrient_key = nutrient["Name"].get<hgps::core::Identifier>();
nutrient_ranges[nutrient_key] = nutrient["Range"].get<std::pair<double, double>>();
if (nutrient_ranges[nutrient_key].first > nutrient_ranges[nutrient_key].second) {
throw hgps::core::HgpsException{
fmt::format("Nutrient range is invalid: {}", nutrient_key.to_string())};
}
nutrient_ranges[nutrient_key] = nutrient["Range"].get<hgps::core::DoubleInterval>();
energy_equation[nutrient_key] = nutrient["Energy"].get<double>();
}

Expand Down Expand Up @@ -310,7 +306,7 @@ load_kevinhall_risk_model_definition(const poco::json &opt, const host::Configur
const auto food_data_table = load_datatable_from_csv(food_data_file_info);

// Rural sector prevalence for age groups and sex.
std::map<hgps::core::Identifier, std::unordered_map<hgps::core::Gender, double>>
std::unordered_map<hgps::core::Identifier, std::unordered_map<hgps::core::Gender, double>>
rural_prevalence;
for (const auto &age_group : opt["RuralPrevalence"]) {
auto age_group_name = age_group["Name"].get<hgps::core::Identifier>();
Expand Down
23 changes: 15 additions & 8 deletions src/HealthGPS.Core/interval.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#pragma once

#include "HealthGPS.Core/exception.h"
#include "forward_type.h"
#include "string_util.h"

#include <algorithm>
#include <fmt/format.h>

namespace hgps::core {
Expand All @@ -16,7 +20,11 @@ template <Numerical TYPE> class Interval {
/// @param lower_value Lower bound value
/// @param upper_value Upper bound value
explicit Interval(TYPE lower_value, TYPE upper_value)
: lower_{lower_value}, upper_{upper_value} {}
: lower_{lower_value}, upper_{upper_value} {
if (lower_ > upper_) {
throw HgpsException(fmt::format("Invalid interval: {}-{}", lower_, upper_));
}
}

/// @brief Gets the interval lower bound
/// @return The lower bound value
Expand All @@ -33,13 +41,7 @@ template <Numerical TYPE> class Interval {
/// @brief Determines whether a value is in the Interval.
/// @param value The value to check
/// @return true if the value is in the interval; otherwise, false.
bool contains(TYPE value) const noexcept {
if (lower_ < upper_) {
return lower_ <= value && value <= upper_;
}

return lower_ >= value && value >= upper_;
}
bool contains(TYPE value) const noexcept { return lower_ <= value && value <= upper_; }
alexdewar marked this conversation as resolved.
Show resolved Hide resolved

/// @brief Determines whether an Interval is inside this instance interval.
/// @param other The other Interval to check
Expand All @@ -48,6 +50,11 @@ template <Numerical TYPE> class Interval {
return contains(other.lower_) && contains(other.upper_);
}

/// @brief Clamp a given value to the interval boundaries
/// @param value The value to clamp
/// @return The clamped value
TYPE clamp(TYPE value) const noexcept { return std::clamp(value, lower_, upper_); }

/// @brief Convert this instance to a string representation
/// @return The equivalent string representation
std::string to_string() const noexcept { return fmt::format("{}-{}", lower_, upper_); }
Expand Down
2 changes: 1 addition & 1 deletion src/HealthGPS.Tests/AgeGenderTable.Test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ TEST(TestHealthGPS_AgeGenderTable, CreateWithWrongRangerThrows) {
using namespace hgps;

auto negative_range = core::IntegerInterval(-1, 10);
auto inverted_range = core::IntegerInterval(10, 1);
auto inverted_range = core::IntegerInterval(1, 1);
alexdewar marked this conversation as resolved.
Show resolved Hide resolved

ASSERT_THROW(create_age_gender_table<double>(negative_range), std::invalid_argument);
ASSERT_THROW(create_age_gender_table<double>(inverted_range), std::invalid_argument);
Expand Down
18 changes: 0 additions & 18 deletions src/HealthGPS.Tests/Interval.Test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,6 @@ TEST(TestCore_Interval, CreatePositive) {
ASSERT_TRUE(animal.contains(dog));
}

TEST(TestCore_Interval, CreateNegative) {
using namespace hgps::core;
auto lower = 0;
auto upper = -10;
auto len = upper - lower;
auto mid = len / 2;
auto animal = IntegerInterval{lower, upper};
auto cat = IntegerInterval{mid, upper};
auto dog = IntegerInterval{lower, mid};

ASSERT_EQ(lower, animal.lower());
ASSERT_EQ(upper, animal.upper());
ASSERT_EQ(len, animal.length());
ASSERT_TRUE(animal.contains(mid));
ASSERT_TRUE(animal.contains(cat));
ASSERT_TRUE(animal.contains(dog));
}

TEST(TestCore_Interval, Comparable) {
using namespace hgps::core;

Expand Down
4 changes: 2 additions & 2 deletions src/HealthGPS/gender_table.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,10 @@ GenderTable<int, TYPE> create_integer_gender_table(const core::IntegerInterval &
/// @tparam TYPE The values data type
/// @param age_range The age breakpoints range
/// @return A new instance of the AgeGenderTable class
/// @throws std::out_of_range for age range 'lower' of negative value or less than the 'upper' value
/// @throws std::out_of_range for age range 'lower' of negative value or equal to the 'upper' value
template <core::Numerical TYPE>
AgeGenderTable<TYPE> create_age_gender_table(const core::IntegerInterval &age_range) {
if (age_range.lower() < 0 || age_range.lower() >= age_range.upper()) {
if (age_range.lower() < 0 || age_range.lower() == age_range.upper()) {
throw std::invalid_argument(
"The 'age lower' value must be greater than zero and less than the 'age upper' value.");
}
Expand Down
50 changes: 22 additions & 28 deletions src/HealthGPS/kevin_hall_model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

#include "HealthGPS.Core/exception.h"

#include <algorithm>
#include <utility>

/*
Expand All @@ -29,11 +28,11 @@ const core::Identifier CI_key{"Carbohydrate"};

KevinHallModel::KevinHallModel(
const std::unordered_map<core::Identifier, double> &energy_equation,
const std::unordered_map<core::Identifier, std::pair<double, double>> &nutrient_ranges,
const std::unordered_map<core::Identifier, core::DoubleInterval> &nutrient_ranges,
const std::unordered_map<core::Identifier, std::map<core::Identifier, double>>
&nutrient_equations,
const std::unordered_map<core::Identifier, std::optional<double>> &food_prices,
const std::map<hgps::core::Identifier, std::unordered_map<hgps::core::Gender, double>>
const std::unordered_map<hgps::core::Identifier, std::unordered_map<hgps::core::Gender, double>>
&rural_prevalence,
jamesturner246 marked this conversation as resolved.
Show resolved Hide resolved
const std::vector<LinearModelParams> &income_models,
const std::unordered_map<core::Gender, std::vector<double>> &age_mean_height)
Expand Down Expand Up @@ -177,31 +176,34 @@ void KevinHallModel::update_sector(RuntimeContext &context, Person &person) cons
void KevinHallModel::initialise_income(RuntimeContext &context, Person &person) const {

// Compute logits for each income category.
auto logits = std::vector<double>(income_models_.size());
for (size_t i = 0; i < income_models_.size(); i++) {
logits[i] = income_models_[i].intercept;
for (const auto &[factor_name, coefficient] : income_models_[i].coefficients) {
logits[i] += coefficient * person.get_risk_factor_value(factor_name);
auto logits = std::vector<double>{};
logits.reserve(income_models_.size());
for (const auto &income_model : income_models_) {
logits.push_back(income_model.intercept);
for (const auto &[factor_name, coefficient] : income_model.coefficients) {
logits.back() += coefficient * person.get_risk_factor_value(factor_name);
}
}

// Compute softmax probabilities for each income category.
auto e_logits = std::vector<double>(income_models_.size());
auto e_logits = std::vector<double>{};
e_logits.reserve(income_models_.size());
double e_logits_sum = 0.0;
for (size_t i = 0; i < income_models_.size(); i++) {
e_logits[i] = exp(logits[i]);
e_logits_sum += e_logits[i];
for (const auto &logit : logits) {
e_logits.push_back(exp(logit));
e_logits_sum += e_logits.back();
}

// Compute income category probabilities.
auto probabilities = std::vector<double>(income_models_.size());
for (size_t i = 0; i < income_models_.size(); i++) {
probabilities[i] = e_logits[i] / e_logits_sum;
auto probabilities = std::vector<double>{};
probabilities.reserve(income_models_.size());
for (const auto &e_logit : e_logits) {
probabilities.push_back(e_logit / e_logits_sum);
}

// Compute income category.
double rand = context.random().next_double();
for (size_t i = 0; i < probabilities.size(); i++) {
for (size_t i = 0; i < income_models_.size(); i++) {
if (rand < probabilities[i]) {
person.income = income_models_[i].name;
return;
Expand All @@ -215,11 +217,9 @@ void KevinHallModel::initialise_income(RuntimeContext &context, Person &person)
void KevinHallModel::update_income(RuntimeContext &context, Person &person) const {

// Only update 18 year olds.
if (person.age != 18) {
return;
if (person.age == 18) {
initialise_income(context, person);
}

initialise_income(context, person);
}

SimulatePersonState KevinHallModel::simulate_person(Person &person, double shift) const {
Expand Down Expand Up @@ -377,18 +377,12 @@ double KevinHallModel::compute_AT(double EI, double EI_0) const {
return beta_AT * delta_EI;
}

double KevinHallModel::bounded_nutrient_value(const core::Identifier &nutrient,
double value) const {
const auto &range = nutrient_ranges_.at(nutrient);
return std::clamp(range.first, range.second, value);
}

KevinHallModelDefinition::KevinHallModelDefinition(
std::unordered_map<core::Identifier, double> energy_equation,
std::unordered_map<core::Identifier, std::pair<double, double>> nutrient_ranges,
std::unordered_map<core::Identifier, core::DoubleInterval> nutrient_ranges,
std::unordered_map<core::Identifier, std::map<core::Identifier, double>> nutrient_equations,
std::unordered_map<core::Identifier, std::optional<double>> food_prices,
std::map<hgps::core::Identifier, std::unordered_map<hgps::core::Gender, double>>
std::unordered_map<hgps::core::Identifier, std::unordered_map<hgps::core::Gender, double>>
rural_prevalence,
std::vector<LinearModelParams> income_models,
std::unordered_map<core::Gender, std::vector<double>> age_mean_height)
Expand Down
28 changes: 11 additions & 17 deletions src/HealthGPS/kevin_hall_model.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,20 +53,20 @@ class KevinHallModel final : public RiskFactorModel {
public:
/// @brief Initialises a new instance of the KevinHallModel class
/// @param energy_equation The energy coefficients for each nutrient
/// @param nutrient_ranges The minimum and maximum nutrient values
/// @param nutrient_ranges The interval boundaries for nutrient values
/// @param nutrient_equations The nutrient coefficients for each food group
/// @param food_prices The unit price for each food group
/// @param rural_prevalence Rural sector prevalence for age groups and sex
/// @param income_models The income models for each income category
/// @param age_mean_height The mean height at all ages (male and female)
KevinHallModel(
const std::unordered_map<core::Identifier, double> &energy_equation,
const std::unordered_map<core::Identifier, std::pair<double, double>> &nutrient_ranges,
const std::unordered_map<core::Identifier, core::DoubleInterval> &nutrient_ranges,
const std::unordered_map<core::Identifier, std::map<core::Identifier, double>>
&nutrient_equations,
const std::unordered_map<core::Identifier, std::optional<double>> &food_prices,
const std::map<hgps::core::Identifier, std::unordered_map<hgps::core::Gender, double>>
&rural_prevalence,
const std::unordered_map<hgps::core::Identifier,
std::unordered_map<hgps::core::Gender, double>> &rural_prevalence,
const std::vector<LinearModelParams> &income_models,
const std::unordered_map<core::Gender, std::vector<double>> &age_mean_height);

Expand All @@ -80,11 +80,11 @@ class KevinHallModel final : public RiskFactorModel {

private:
const std::unordered_map<core::Identifier, double> &energy_equation_;
const std::unordered_map<core::Identifier, std::pair<double, double>> &nutrient_ranges_;
const std::unordered_map<core::Identifier, core::DoubleInterval> &nutrient_ranges_;
const std::unordered_map<core::Identifier, std::map<core::Identifier, double>>
&nutrient_equations_;
const std::unordered_map<core::Identifier, std::optional<double>> &food_prices_;
const std::map<hgps::core::Identifier, std::unordered_map<hgps::core::Gender, double>>
const std::unordered_map<hgps::core::Identifier, std::unordered_map<hgps::core::Gender, double>>
&rural_prevalence_;
const std::vector<LinearModelParams> &income_models_;
const std::unordered_map<core::Gender, std::vector<double>> &age_mean_height_;
Expand Down Expand Up @@ -178,20 +178,14 @@ class KevinHallModel final : public RiskFactorModel {
/// @param EI_0 The initial energy intake
/// @return The computed adaptive thermogenesis
double compute_AT(double EI, double EI_0) const;

/// @brief Return the nutrient value bounded within its range
/// @param nutrient The nutrient Identifier
/// @param value The nutrient value to bound
/// @return The bounded nutrient value
double bounded_nutrient_value(const core::Identifier &nutrient, double value) const;
};

/// @brief Defines the energy balance model data type
class KevinHallModelDefinition final : public RiskFactorModelDefinition {
public:
/// @brief Initialises a new instance of the KevinHallModelDefinition class
/// @param energy_equation The energy coefficients for each nutrient
/// @param nutrient_ranges The minimum and maximum nutrient values
/// @param nutrient_ranges The interval boundaries for nutrient values
/// @param nutrient_equations The nutrient coefficients for each food group
/// @param food_prices The unit price for each food group
/// @param rural_prevalence Rural sector prevalence for age groups and sex
Expand All @@ -200,10 +194,10 @@ class KevinHallModelDefinition final : public RiskFactorModelDefinition {
/// @throws std::invalid_argument for empty arguments
KevinHallModelDefinition(
std::unordered_map<core::Identifier, double> energy_equation,
std::unordered_map<core::Identifier, std::pair<double, double>> nutrient_ranges,
std::unordered_map<core::Identifier, core::DoubleInterval> nutrient_ranges,
std::unordered_map<core::Identifier, std::map<core::Identifier, double>> nutrient_equations,
std::unordered_map<core::Identifier, std::optional<double>> food_prices,
std::map<hgps::core::Identifier, std::unordered_map<hgps::core::Gender, double>>
std::unordered_map<hgps::core::Identifier, std::unordered_map<hgps::core::Gender, double>>
rural_prevalence,
std::vector<LinearModelParams> income_models,
std::unordered_map<core::Gender, std::vector<double>> age_mean_height);
Expand All @@ -214,10 +208,10 @@ class KevinHallModelDefinition final : public RiskFactorModelDefinition {

private:
std::unordered_map<core::Identifier, double> energy_equation_;
std::unordered_map<core::Identifier, std::pair<double, double>> nutrient_ranges_;
std::unordered_map<core::Identifier, core::DoubleInterval> nutrient_ranges_;
std::unordered_map<core::Identifier, std::map<core::Identifier, double>> nutrient_equations_;
std::unordered_map<core::Identifier, std::optional<double>> food_prices_;
std::map<hgps::core::Identifier, std::unordered_map<hgps::core::Gender, double>>
std::unordered_map<hgps::core::Identifier, std::unordered_map<hgps::core::Gender, double>>
rural_prevalence_;
std::vector<LinearModelParams> income_models_;
std::unordered_map<core::Gender, std::vector<double>> age_mean_height_;
Expand Down