Skip to content

Performance test: check deep AST using sum() operator #34

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

Merged
merged 5 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
86 changes: 60 additions & 26 deletions src/andromede/libs/standard.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,32 +63,6 @@
.sum()
.expec(),
)
"""
A standard model for a linear cost generation, limited by a maximum generation.
"""
GENERATOR_MODEL = model(
id="GEN",
parameters=[
float_parameter("p_max", CONSTANT),
float_parameter("cost", CONSTANT),
],
variables=[float_variable("generation", lower_bound=literal(0))],
ports=[ModelPort(port_type=BALANCE_PORT_TYPE, port_name="balance_port")],
port_fields_definitions=[
PortFieldDefinition(
port_field=PortFieldId("balance_port", "flow"),
definition=var("generation"),
)
],
constraints=[
Constraint(
name="Max generation", expression=var("generation") <= param("p_max")
),
],
objective_operational_contribution=(param("cost") * var("generation"))
.sum()
.expec(),
)

"""
Basic link model using ports
Expand Down Expand Up @@ -132,6 +106,33 @@
],
)

"""
A standard model for a linear cost generation, limited by a maximum generation.
"""
GENERATOR_MODEL = model(
id="GEN",
parameters=[
float_parameter("p_max", CONSTANT),
float_parameter("cost", CONSTANT),
],
variables=[float_variable("generation", lower_bound=literal(0))],
ports=[ModelPort(port_type=BALANCE_PORT_TYPE, port_name="balance_port")],
port_fields_definitions=[
PortFieldDefinition(
port_field=PortFieldId("balance_port", "flow"),
definition=var("generation"),
)
],
constraints=[
Constraint(
name="Max generation", expression=var("generation") <= param("p_max")
),
],
objective_operational_contribution=(param("cost") * var("generation"))
.sum()
.expec(),
)

GENERATOR_MODEL_WITH_PMIN = model(
id="GEN",
parameters=[
Expand Down Expand Up @@ -162,6 +163,39 @@
.expec(),
)

"""
A model for a linear cost generation limited by a maximum generation per time-step
and total generation in whole period. It considers a full storage with no replenishing
"""
GENERATOR_MODEL_WITH_STORAGE = model(
id="GEN",
parameters=[
float_parameter("p_max", CONSTANT),
float_parameter("cost", CONSTANT),
float_parameter("full_storage", CONSTANT),
],
variables=[float_variable("generation", lower_bound=literal(0))],
ports=[ModelPort(port_type=BALANCE_PORT_TYPE, port_name="balance_port")],
port_fields_definitions=[
PortFieldDefinition(
port_field=PortFieldId("balance_port", "flow"),
definition=var("generation"),
)
],
constraints=[
Constraint(
name="Max generation", expression=var("generation") <= param("p_max")
),
Constraint(
name="Total storage",
expression=var("generation").sum() <= param("full_storage"),
),
],
objective_operational_contribution=(param("cost") * var("generation"))
.sum()
.expec(),
)

# For now, no starting cost
THERMAL_CLUSTER_MODEL_HD = model(
id="GEN",
Expand Down
170 changes: 150 additions & 20 deletions tests/functional/test_performance.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@
#
# This file is part of the Antares project.

from andromede.expression.expression import literal, param
from typing import cast

from andromede.expression.expression import ExpressionNode, literal, param, var
from andromede.expression.indexing_structure import IndexingStructure
from andromede.libs.standard import (
BALANCE_PORT_TYPE,
DEMAND_MODEL,
GENERATOR_MODEL,
GENERATOR_MODEL_WITH_STORAGE,
NODE_BALANCE_MODEL,
)
from andromede.model import ModelPort, float_parameter, model
from andromede.model import float_parameter, float_variable, model
from andromede.simulation import TimeBlock, build_problem
from andromede.study import (
ConstantData,
Expand All @@ -28,15 +31,17 @@
PortRef,
create_component,
)
from tests.unittests.test_utils import generate_data
from tests.unittests.test_utils import generate_scalar_matrix_data


def test_large_sum_with_loop() -> None:
"""
Test performance when the problem involves an expression with a high number of terms. Here the objective function is the sum over nb_terms terms.
def test_large_sum_inside_model_with_loop() -> None:
"""
Test performance when the problem involves an expression with a high number of terms.
Here the objective function is the sum over nb_terms terms on a for-loop inside the model

# This test pass with 476 terms but fails with 477 locally due to recursion depth, and even less terms are possible with Jenkins...
This test pass with 476 terms but fails with 477 locally due to recursion depth,
and even less terms are possible with Jenkins...
"""
nb_terms = 100

time_blocks = [TimeBlock(0, [0])]
Expand All @@ -52,14 +57,12 @@ def test_large_sum_with_loop() -> None:
float_parameter(f"cost_{i}", IndexingStructure(False, False))
for i in range(1, nb_terms)
],
objective_operational_contribution=sum(
[param(f"cost_{i}") for i in range(1, nb_terms)]
objective_operational_contribution=cast(
ExpressionNode, sum(param(f"cost_{i}") for i in range(1, nb_terms))
),
)

network = Network("test")

# for i in range(1, nb_terms + 1):
cost_model = create_component(model=SIMPLE_COST_MODEL, id="simple_cost")
network.add_component(cost_model)

Expand All @@ -72,12 +75,12 @@ def test_large_sum_with_loop() -> None:
)


def test_large_sum_outside_model() -> None:
def test_large_sum_outside_model_with_loop() -> None:
"""
Test performance when the problem involves an expression with a high number of terms. Here the objective function is the sum over nb_terms terms.
Test performance when the problem involves an expression with a high number of terms.
Here the objective function is the sum over nb_terms terms on a for-loop outside the model
"""

nb_terms = 10000
nb_terms = 10_000

time_blocks = [TimeBlock(0, [0])]
scenarios = 1
Expand All @@ -93,7 +96,6 @@ def test_large_sum_outside_model() -> None:

network = Network("test")

# for i in range(1, nb_terms + 1):
simple_model = create_component(
model=SIMPLE_COST_MODEL,
id="simple_cost",
Expand All @@ -104,10 +106,98 @@ def test_large_sum_outside_model() -> None:
status = problem.solver.Solve()

assert status == problem.solver.OPTIMAL
assert problem.solver.Objective().Value() == sum(
[1 / i for i in range(1, nb_terms)]
assert problem.solver.Objective().Value() == obj_coeff


def test_large_sum_inside_model_with_sum_operator() -> None:
"""
Test performance when the problem involves an expression with a high number of terms.
Here the objective function is the sum over nb_terms terms with the sum() operator inside the model
"""
nb_terms = 10_000

scenarios = 1
time_blocks = [TimeBlock(0, list(range(nb_terms)))]
database = DataBase()

# Weird values when the "cost" varies over time and we use the sum() operator:
# For testing purposes, will use a const value since the problem seems to come when
# we try to linearize nb_terms variables with nb_terms distinct parameters
# TODO check the sum() operator for time-variable parameters
database.add_data("simple_cost", "cost", ConstantData(3))

SIMPLE_COST_MODEL = model(
id="SIMPLE_COST",
parameters=[
float_parameter("cost", IndexingStructure(False, False)),
],
variables=[
float_variable(
"var",
lower_bound=literal(1),
upper_bound=literal(1),
structure=IndexingStructure(True, False),
),
],
objective_operational_contribution=(param("cost") * var("var")).sum(),
)

network = Network("test")

cost_model = create_component(model=SIMPLE_COST_MODEL, id="simple_cost")
network.add_component(cost_model)

problem = build_problem(network, database, time_blocks[0], scenarios)
status = problem.solver.Solve()

assert status == problem.solver.OPTIMAL
assert problem.solver.Objective().Value() == 3 * nb_terms


def test_large_sum_of_port_connections() -> None:
"""
Test performance when the problem involves a model where several generators are connected to a node.

This test pass with 470 terms but fails with 471 locally due to recursion depth,
and possibly even less terms are possible with Jenkins...
"""
nb_generators = 100

time_block = TimeBlock(0, [0])
scenarios = 1

database = DataBase()
database.add_data("D", "demand", ConstantData(nb_generators))

for gen_id in range(nb_generators):
database.add_data(f"G_{gen_id}", "p_max", ConstantData(1))
database.add_data(f"G_{gen_id}", "cost", ConstantData(5))

node = Node(model=NODE_BALANCE_MODEL, id="N")
demand = create_component(model=DEMAND_MODEL, id="D")
generators = [
create_component(model=GENERATOR_MODEL, id=f"G_{gen_id}")
for gen_id in range(nb_generators)
]

network = Network("test")
network.add_node(node)

network.add_component(demand)
network.connect(PortRef(demand, "balance_port"), PortRef(node, "balance_port"))

for gen_id in range(nb_generators):
network.add_component(generators[gen_id])
network.connect(
PortRef(generators[gen_id], "balance_port"), PortRef(node, "balance_port")
)

problem = build_problem(network, database, time_block, scenarios)
status = problem.solver.Solve()

assert status == problem.solver.OPTIMAL
assert problem.solver.Objective().Value() == 5 * nb_generators


def test_basic_balance_on_whole_year() -> None:
"""
Expand All @@ -119,7 +209,9 @@ def test_basic_balance_on_whole_year() -> None:
time_block = TimeBlock(1, list(range(horizon)))

database = DataBase()
database.add_data("D", "demand", generate_data(100, horizon, scenarios))
database.add_data(
"D", "demand", generate_scalar_matrix_data(100, horizon, scenarios)
)

database.add_data("G", "p_max", ConstantData(100))
database.add_data("G", "cost", ConstantData(30))
Expand All @@ -140,4 +232,42 @@ def test_basic_balance_on_whole_year() -> None:
status = problem.solver.Solve()

assert status == problem.solver.OPTIMAL
assert problem.solver.Objective().Value() == 3000 * horizon
assert problem.solver.Objective().Value() == 30 * 100 * horizon


def test_basic_balance_on_whole_year_with_large_sum() -> None:
"""
Balance on one node with one fixed demand and one generation with storage, on 8760 timestep.
"""

scenarios = 1
horizon = 8760
time_block = TimeBlock(1, list(range(horizon)))

database = DataBase()
database.add_data(
"D", "demand", generate_scalar_matrix_data(100, horizon, scenarios)
)

database.add_data("G", "p_max", ConstantData(100))
database.add_data("G", "cost", ConstantData(30))
database.add_data("G", "full_storage", ConstantData(100 * horizon))

node = Node(model=NODE_BALANCE_MODEL, id="N")
demand = create_component(model=DEMAND_MODEL, id="D")
gen = create_component(
model=GENERATOR_MODEL_WITH_STORAGE, id="G"
) # Limits the total generation inside a TimeBlock

network = Network("test")
network.add_node(node)
network.add_component(demand)
network.add_component(gen)
network.connect(PortRef(demand, "balance_port"), PortRef(node, "balance_port"))
network.connect(PortRef(gen, "balance_port"), PortRef(node, "balance_port"))
Comment on lines +273 to +274
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are port connections going to work in YAML files ? Defined in the model ? Defined in the study ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure. @sylvlecl or @vargastat would know it better


problem = build_problem(network, database, time_block, scenarios)
status = problem.solver.Solve()

assert status == problem.solver.OPTIMAL
assert problem.solver.Objective().Value() == 30 * 100 * horizon
20 changes: 14 additions & 6 deletions tests/functional/test_stochastic.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
PortRef,
create_component,
)
from tests.unittests.test_utils import generate_data
from tests.unittests.test_utils import generate_scalar_matrix_data


@pytest.fixture
Expand All @@ -46,11 +46,19 @@ def scenarios() -> int:
def database(horizon: int, scenarios: int) -> DataBase:
database = DataBase()

database.add_data("D", "demand", generate_data(500, horizon, scenarios))

database.add_data("BASE", "nb_failures", generate_data(1, horizon, scenarios))
database.add_data("SEMIBASE", "nb_failures", generate_data(1, horizon, scenarios))
database.add_data("PEAK", "nb_failures", generate_data(1, horizon, scenarios))
database.add_data(
"D", "demand", generate_scalar_matrix_data(500, horizon, scenarios)
)

database.add_data(
"BASE", "nb_failures", generate_scalar_matrix_data(1, horizon, scenarios)
)
database.add_data(
"SEMIBASE", "nb_failures", generate_scalar_matrix_data(1, horizon, scenarios)
)
database.add_data(
"PEAK", "nb_failures", generate_scalar_matrix_data(1, horizon, scenarios)
)

database.add_data("BASE", "p_max", ConstantData(250))
database.add_data("BASE", "p_min", ConstantData(100))
Expand Down
Loading
Loading