Skip to content

Commit f808d19

Browse files
committed
fix: support create FBA with no data
1 parent 81f570b commit f808d19

5 files changed

Lines changed: 27 additions & 12 deletions

File tree

‎corneto/_data.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@ def from_cdict(cls, d: dict) -> "Data":
141141
samples[sample_key] = Sample(features)
142142
return cls(samples)
143143

144+
@classmethod
145+
def empty(cls) -> "Data":
146+
return cls.from_cdict({"": {}})
147+
144148
def to_json(self, **kwargs) -> str:
145149
return json.dumps(self.to_dict(), **kwargs)
146150

‎corneto/methods/future/method.py‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ def preprocess(self, graph: BaseGraph, data: Data) -> Tuple[BaseGraph, Data]:
142142
pass
143143

144144
@abstractmethod
145-
def create_problem(self, graph: BaseGraph, data: Data):
145+
def create_problem(self, graph: BaseGraph, data: Data) -> ProblemDef:
146146
"""Create the optimization problem.
147147
148148
This method should define variables, constraints, and objectives according to the method's
@@ -157,7 +157,7 @@ def create_problem(self, graph: BaseGraph, data: Data):
157157
"""
158158
pass
159159

160-
def build(self, graph: BaseGraph, data: Data) -> ProblemDef:
160+
def build(self, graph: BaseGraph, data: Optional[Data] = None) -> ProblemDef:
161161
"""Build the complete optimization problem.
162162
163163
The process involves:
@@ -175,6 +175,8 @@ def build(self, graph: BaseGraph, data: Data) -> ProblemDef:
175175
Raises:
176176
ValueError: If lambda_reg > 0 but no regularization variable name is provided.
177177
"""
178+
if data is None:
179+
data = Data.empty()
178180
self.processed_graph, self.processed_data = self.preprocess(graph, data)
179181
self.problem = self.create_problem(self.processed_graph, self.processed_data)
180182

@@ -502,7 +504,7 @@ def create_problem(self, graph: BaseGraph, data: Data):
502504
# is selected or not, to the edges in the original graph, using the selected_edges list that
503505
# contains the indexes of the edges in the original graph. The variable selected_edges contains
504506
# None for edges that are not selected in the original graph, those we ignore.
505-
# --- Step 1: Filter out None entries ---
507+
506508
# Create a boolean mask that is True for valid indices (not None)
507509
valid_mask = np.array([edge is not None for edge in selected_edges])
508510

@@ -512,7 +514,7 @@ def create_problem(self, graph: BaseGraph, data: Data):
512514
# Extract the valid edge indices (they are already 0-indexed)
513515
valid_edges = np.array([edge for edge in selected_edges if edge is not None])
514516

515-
# --- Step 2: Build the indicator matrix E ---
517+
# Build the indicator matrix E
516518
# m is the number of edges in the original graph
517519
m = graph.num_edges # Total number of rows in the target vector
518520

@@ -524,7 +526,7 @@ def create_problem(self, graph: BaseGraph, data: Data):
524526
# resulting in an indicator matrix E of shape (m, n_valid).
525527
E = (rows == valid_edges.reshape(1, -1)).astype(int)
526528

527-
# --- Step 3: Matrix multiplication to scatter the values ---
529+
# Matrix multiplication to scatter the values
528530
# Here the "@" operator multiplies the indicator matrix E with valid_vec.
529531
# Y is then a vector of shape (m, 1) with the values from vec mapped
530532
# to their corresponding positions in the original graph.

‎corneto/methods/future/pcst.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111

1212
class PrizeCollectingSteinerTree(FlowMethod):
13-
"""Prize-Collecting Steiner Tree optimization method as a flow-based problem.
13+
"""Prize-Collecting Steiner Tree optimization method built on top of FlowMethod.
1414
1515
In a prize-collecting Steiner tree problem, terminals can have prizes (values > 0),
1616
making them optional terminals that provide a benefit if included in the solution.
@@ -298,7 +298,7 @@ def create_flow_based_problem(self, flow_problem: ProblemDef, graph: BaseGraph,
298298
if other_flow_edges:
299299
flow_problem += F[other_flow_edges] == 0
300300

301-
# --- Root Flow Constraints ---
301+
# Root Flow Constraints
302302
if sample_selected_root is not None:
303303
if self.force_flow_through_root:
304304
flow_problem += F[self.flow_edges_in[sample_selected_root]] == sample_max_flow
@@ -331,7 +331,7 @@ def create_flow_based_problem(self, flow_problem: ProblemDef, graph: BaseGraph,
331331
terminal_neg
332332
][t_idx].sum() == len(t_idx)
333333

334-
# --- Costs Objective ---
334+
# Costs Objective
335335
edge_costs = np.ones((graph.num_edges)) * self.default_edge_cost
336336
selected = with_flow if len(with_flow.shape) == 1 else with_flow[:, i]
337337

@@ -345,7 +345,7 @@ def create_flow_based_problem(self, flow_problem: ProblemDef, graph: BaseGraph,
345345

346346
flow_problem.add_objective(edge_costs[edge_ids] @ selected[edge_ids], name="edge_cost")
347347

348-
# --- Prizes Objective (PCST) ---
348+
# Prizes Objective (PCST)
349349
if prized_terminals:
350350
prized_idx = []
351351
prized_vertices = []

‎corneto/methods/metabolism/fba.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,11 @@ def fba_problem(G, create_reaction_indicators=False, num_fluxes=1, eps=1e-4, bac
8181
8282
Args:
8383
G (BaseGraph): The metabolic network graph.
84-
create_reaction_indicators (bool, optional): Whether to create binary indicators
85-
for reactions. Defaults to False.
84+
create_reaction_indicators (bool, optional): Whether to create binary positive
85+
and negative indicators for reaction fluxes. Defaults to False.
8686
num_fluxes (int, optional): Number of flux variables per reaction. Defaults to 1.
87-
eps (float, optional): Tolerance for considering a flux as non-zero. Defaults to 1e-4.
87+
eps (float, optional): Tolerance for considering a flux as non-zero. Only used
88+
with indicators. Defaults to 1e-4.
8889
backend (Backend, optional): The optimization backend to use. Defaults to K.
8990
9091
Returns:

‎tests/methods/future/test_fba_future.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ def metabolic_network():
1717
return import_miom_model(file_path)
1818

1919

20+
def test_fba_without_data(metabolic_network, backend):
21+
"""Test a standard FBA problem with no objective."""
22+
fba = MultiSampleFBA(backend=backend)
23+
P = fba.build(metabolic_network)
24+
result = P.solve()
25+
assert result.status == "optimal"
26+
27+
2028
def test_single_sample_standard_fba(metabolic_network, backend):
2129
"""Test the standard FBA method with a single sample."""
2230
fba = MultiSampleFBA(backend=backend)

0 commit comments

Comments
 (0)