Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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

Large diffs are not rendered by default.

Binary file not shown.
73 changes: 73 additions & 0 deletions pythia/biochar_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# pythia/biochar_model.py

def _interpolate(x, x_points, y_points):
"""
Performs linear interpolation.
"""
if x <= x_points[0]:
return y_points[0]
if x >= x_points[-1]:
return y_points[-1]

for i in range(len(x_points) - 1):
if x_points[i] <= x < x_points[i+1]:
y = y_points[i] + (x - x_points[i]) * (y_points[i+1] - y_points[i]) / (x_points[i+1] - x_points[i])
return y
return y_points[-1]

def calculate_biochar_impact(biochar_rate, years_since_application):
"""
Calculates the impact of biochar on maize growth based on application rate
and time since application.

The model is based on the findings from "Long-term effects of biochar
application on the growth and physiological characteristics of maize"
(Cong et al., 2023).

Args:
biochar_rate (float): Biochar application rate in t ha-1.
years_since_application (int): Years since biochar was applied.

Returns:
dict: A dictionary with impact factors for yield and biomass.
"""

# Data points from the paper for yield factor
# Short-term (new application)
short_term_rates = [0, 15.75, 31.50, 63.00, 126.00]
short_term_yield_factors = [1.0, 1.012, 1.0846, 1.032, 0.6871]

# Long-term (7 years after application)
long_term_rates = [0, 15.75, 31.50, 63.00, 126.00]
long_term_yield_factors = [1.0, 1.3325, 1.4038, 1.4580, 1.6294]

# Data points for biomass factor
# Short-term: estimates from paper text and Figure 3A
short_term_biomass_factors = [1.0, 1.12, 1.2222, 0.96, 0.60]

# Long-term: estimates from paper text and Figure 3B
long_term_biomass_factors = [1.0, 1.14, 1.25, 1.39, 1.57]

# Interpolate to get factors for the given biochar_rate
st_yield_factor = _interpolate(biochar_rate, short_term_rates, short_term_yield_factors)
lt_yield_factor = _interpolate(biochar_rate, long_term_rates, long_term_yield_factors)
st_biomass_factor = _interpolate(biochar_rate, short_term_rates, short_term_biomass_factors)
lt_biomass_factor = _interpolate(biochar_rate, long_term_rates, long_term_biomass_factors)

# Interpolate between short-term and long-term based on years_since_application
# Assuming linear transition from year 0 to year 7.
if years_since_application < 0:
years_since_application = 0

if years_since_application >= 7:
yield_factor = lt_yield_factor
biomass_factor = lt_biomass_factor
else:
weight = years_since_application / 7.0
yield_factor = st_yield_factor * (1 - weight) + lt_yield_factor * weight
biomass_factor = st_biomass_factor * (1 - weight) + lt_biomass_factor * weight

return {
'yield_factor': yield_factor,
'biomass_factor': biomass_factor,
}
2 changes: 1 addition & 1 deletion pythia/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def run_plugin_functions(hook, plugins, **kwargs):
_return = {**kwargs}
if hook in plugins:
for plugin_fun in plugins[hook]:
plugin_fun_return = plugin_fun["fun"](plugin_fun.get("config", {}), _return, **kwargs)
plugin_fun_return = plugin_fun["fun"](plugin_fun.get("config", {}), **kwargs)
_return = {
**_return,
**({} if plugin_fun_return is None else plugin_fun_return)
Expand Down
3 changes: 2 additions & 1 deletion pythia/plugins/test_plugin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ def sample_function(config={}, **kwargs):
return {**kwargs, "config": config, "retval": retval}


def contexted_function(context={}, **kwargs):
def contexted_function(config, **kwargs):
logging.info("[TEST PLUGIN] Running the contexted_function()")
context = kwargs.get("context", {})
context["context_value"] = context.get("context_value", 2) + 1
return {**kwargs, "context": context}

Expand Down
30 changes: 18 additions & 12 deletions pythia/tests/plugin_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,14 @@ def test_load_plugin():
plugins1 = {}
plugins1 = load_plugins(config, plugins)
assert PluginHook.post_config in plugins1
assert plugins1[PluginHook.post_config] == [
{"fun": pythia.plugins.test_plugin.sample_function, "config": {}}
]
assert plugins1 != {
PluginHook.post_config: [{"fun": sample_function, "config": {}}]
assert len(plugins1[PluginHook.post_config]) == 1
assert (
plugins1[PluginHook.post_config][0]["fun"]
== pythia.plugins.test_plugin.sample_function
)
assert plugins1[PluginHook.post_config][0]["config"] == {
"plugin": "test_plugin",
"params": {},
}


Expand All @@ -81,8 +84,9 @@ def test_plugin_manual_execution():
plugins1 = {}
plugins1 = load_plugins(config, plugins)

for plugin in plugins1[PluginHook.post_config]:
assert 1 == plugin["fun"]()
plugin = plugins1[PluginHook.post_config][0]
result = plugin["fun"](config=plugin["config"])
assert result["retval"] == 1


def test_plugin_auto_execution():
Expand All @@ -92,11 +96,13 @@ def test_plugin_auto_execution():
plugins1 = load_plugins(config, plugins)
context = {"context_value": 7}
context1 = run_plugin_functions(
PluginHook.post_build_context, plugins1, context=context
PluginHook.post_build_context, plugins1, context=context.copy()
).get("context")
context2 = run_plugin_functions(PluginHook.post_build_context, plugins1).get("context", None)
assert context1 != context
context2 = run_plugin_functions(PluginHook.post_build_context, plugins1).get(
"context", None
)
assert context1["context_value"] == 8
assert context["context_value"] == 7
assert context2["context_value"] == 3


Expand All @@ -106,11 +112,11 @@ def test_no_plugin_does_not_change_context():
plugins1 = load_plugins(config, plugins)
context = {"hello": "there"}
context1 = run_plugin_functions(
PluginHook.post_build_context, plugins, context=context
PluginHook.post_build_context, plugins, context=context.copy()
).get("context")
assert context == context1
context2 = run_plugin_functions(
PluginHook.post_build_context, plugins1, context=context
PluginHook.post_build_context, plugins1, context=context.copy()
).get("context")
assert context1 != context2
assert context2 == {**context, **{"context_value": 3}}
49 changes: 49 additions & 0 deletions pythia/tests/test_biochar_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from pythia import biochar_model

def test_calculate_biochar_impact_new_application():
# Test new application (year 0)
impact = biochar_model.calculate_biochar_impact(31.50, 0)
assert abs(impact['yield_factor'] - 1.0846) < 1e-4
assert abs(impact['biomass_factor'] - 1.2222) < 1e-4

impact = biochar_model.calculate_biochar_impact(126.00, 0)
assert abs(impact['yield_factor'] - 0.6871) < 1e-4
assert abs(impact['biomass_factor'] - 0.60) < 1e-4

def test_calculate_biochar_impact_long_term_application():
# Test long-term application (year 7)
impact = biochar_model.calculate_biochar_impact(31.50, 7)
assert abs(impact['yield_factor'] - 1.4038) < 1e-4
assert abs(impact['biomass_factor'] - 1.25) < 1e-4

impact = biochar_model.calculate_biochar_impact(126.00, 8) # test >= 7
assert abs(impact['yield_factor'] - 1.6294) < 1e-4
assert abs(impact['biomass_factor'] - 1.57) < 1e-4

def test_calculate_biochar_impact_interpolation_rate():
# Test interpolation between rates
impact = biochar_model.calculate_biochar_impact(23.625, 0) # halfway between 15.75 and 31.50
expected_yield = 1.012 + (1.0846 - 1.012) / 2
assert abs(impact['yield_factor'] - expected_yield) < 1e-4

def test_calculate_biochar_impact_interpolation_year():
# Test interpolation between years
impact = biochar_model.calculate_biochar_impact(31.50, 3.5) # halfway between year 0 and 7
st_yield = 1.0846
lt_yield = 1.4038
expected_yield = st_yield * 0.5 + lt_yield * 0.5
assert abs(impact['yield_factor'] - expected_yield) < 1e-4

def test_calculate_biochar_impact_edge_cases():
# Test zero rate
impact = biochar_model.calculate_biochar_impact(0, 5)
assert abs(impact['yield_factor'] - 1.0) < 1e-4
assert abs(impact['biomass_factor'] - 1.0) < 1e-4

# Test rate > max rate
impact = biochar_model.calculate_biochar_impact(150, 0)
assert abs(impact['yield_factor'] - 0.6871) < 1e-4

# Test negative years
impact = biochar_model.calculate_biochar_impact(31.50, -1)
assert abs(impact['yield_factor'] - 1.0846) < 1e-4