Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ my_graph.pkl.gz
tests/_notebooks/
.asv/results/
CLAUDE.md
benchmarks/
resources/


########################
Expand Down
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ repos:
- id: pre-commit-update

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.0
rev: v0.15.17
hooks:
- id: ruff
args:
Expand All @@ -44,13 +44,13 @@ repos:
- id: check-yaml

- repo: https://github.com/compilerla/conventional-pre-commit
rev: v4.3.0
rev: v4.4.0
hooks:
- id: conventional-pre-commit
stages: [commit-msg]

- repo: https://github.com/python-poetry/poetry
rev: 2.3.2
rev: 2.4.1
hooks:
- id: poetry-check
- id: poetry-lock
Expand Down
23 changes: 16 additions & 7 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,24 @@ Useful options:
```bash
poetry run release v1.0.0-beta.4 --dry-run # validate only
poetry run release v1.0.0-beta.4 --yes # skip confirmation prompt
poetry run release v1.0.0-beta.4 --remote public # use an explicit release remote
```

The remote defaults to `origin`, which is appropriate for a normal clone of
the public CORNETO repository. In a private development workbench where
`origin` points to a private mirror, configure a separate release-only remote
for the public repository and pass it explicitly:

```bash
git remote add public https://github.com/saezlab/corneto.git
poetry run release v1.0.0-beta.8 --remote public --dry-run
poetry run release v1.0.0-beta.8 --remote public
```

Before tagging, publish selected private changes through a clean branch and a
pull request into public `dev`, followed by a release pull request from public
`dev` into public `main`. Never release a private development branch directly.

### Customizing Release Notes

After the automated release is created, you can:
Expand Down Expand Up @@ -237,13 +253,6 @@ The version switcher uses a **single, centrally-updated file hosted on GitHub Pa
- This ensures clean regeneration for a given version (no mixing of old/new files) while leaving other versions untouched.
- The root redirect is deployed with `keep_files: true` so it doesn’t delete any versioned folders.

#### Benefits of This Approach

- **Central Management**: One `switcher.json` serves all deployed documentation versions
- **Automatic Updates**: Old deployed docs automatically show new versions in the switcher dropdown
- **No Regeneration Needed**: Previously deployed documentation doesn't need to be rebuilt to show new versions
- **Consistency**: All documentation versions display the same version list

#### Manual Switcher Updates

If the automatic workflow fails or manual intervention is needed, re-run the docs workflow or regenerate and deploy the root artifacts (redirect + `switcher.json`) using the same workflow steps.
53 changes: 43 additions & 10 deletions corneto/backend/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1048,8 +1048,8 @@ def Acyclic0(
if In is not None:
# NOTE: Negative flows eq. to reversed directed edge
# Get edges s->t that can have a positive flow
if hasattr(In, "lb"):
e_neg = [(i, g.get_edge(i)) for i in np.flatnonzero(In.lb < 0)]
if hasattr(In, "ub"):
e_neg = [(i, g.get_edge(i)) for i in np.flatnonzero(In.ub > 0)]
e_ix = np.array([i for i, (s, t) in e_neg if len(s) > 0 and len(t) > 0])
else:
e_ix = np.array([i for i, (s, t) in enumerate(g.E) if len(s) > 0 and len(t) > 0])
Expand Down Expand Up @@ -1192,9 +1192,9 @@ def Acyclic(

if In is not None:
# Negative flows are handled as reversed directed edges.
if hasattr(In, "lb"):
lb = In.lb if len(In.shape) == 1 else In.lb[:, i_sample]
e_neg = [(i, g.get_edge(i)) for i in np.flatnonzero(lb < 0)]
if hasattr(In, "ub"):
ub = In.ub if len(In.shape) == 1 else In.ub[:, i_sample]
e_neg = [(i, g.get_edge(i)) for i in np.flatnonzero(ub > 0)]
e_ix = np.array([i for i, (s, t) in e_neg if s and t])
else:
e_ix = np.array([i for i, (s, t) in enumerate(g.E) if s and t])
Expand Down Expand Up @@ -1351,9 +1351,18 @@ def Indicator(
I = self.Variable(name, S.shape, 0, 1, vartype=VarType.BINARY)
blocked = np.isclose(ub, 0) & np.isclose(lb, 0)
if np.sum(blocked) > 0:
# indexing compatible with CVXPY and PICOS
idx = np.where(blocked)[0]
c += [I[idx] == 0, S[idx] == 0]
if blocked.ndim == 0:
c += [I == 0, S == 0]
elif blocked.ndim == 1:
idx = np.where(blocked)[0]
c += [I[idx] == 0, S[idx] == 0]
elif blocked.ndim == 2:
for col in range(blocked.shape[1]):
idx = np.where(blocked[:, col])[0]
if len(idx) > 0:
c += [I[idx, col] == 0, S[idx, col] == 0]
else:
raise ValueError(f"Unsupported indicator bounds dimensionality: {blocked.ndim}")
# Add constraint: lb * I <= V <= ub * I
if V._provided_lb is None or V._provided_ub is None:
raise ValueError(f"The continuous variable {V.name} is unbounded, indicators cannot be created.")
Expand Down Expand Up @@ -1410,9 +1419,33 @@ def NonZeroIndicator(

# Disable infeasible binary indicators based on bounds
if np.sum(ub <= 0) > 0:
c += [I_pos[np.where(ub <= 0)[0]] == 0]
mask = ub <= 0
if mask.ndim == 0:
c += [I_pos == 0]
elif mask.ndim == 1:
idx = np.where(mask)[0]
c += [I_pos[idx] == 0]
elif mask.ndim == 2:
for col in range(mask.shape[1]):
idx = np.where(mask[:, col])[0]
if len(idx) > 0:
c += [I_pos[idx, col] == 0]
else:
raise ValueError(f"Unsupported NonZeroIndicator ub dimensionality: {mask.ndim}")
if np.sum(lb >= 0) > 0:
c += [I_neg[np.where(lb >= 0)[0]] == 0]
mask = lb >= 0
if mask.ndim == 0:
c += [I_neg == 0]
elif mask.ndim == 1:
idx = np.where(mask)[0]
c += [I_neg[idx] == 0]
elif mask.ndim == 2:
for col in range(mask.shape[1]):
idx = np.where(mask[:, col])[0]
if len(idx) > 0:
c += [I_neg[idx, col] == 0]
else:
raise ValueError(f"Unsupported NonZeroIndicator lb dimensionality: {mask.ndim}")

# Add constraints to enforce variable behavior depending on the indicator activation:
# If I_pos = 1 and I_neg = 0: V >= tol AND V <= ub
Expand Down
Loading
Loading