Skip to content

Commit a9c19b6

Browse files
committed
Merge branch 'master' into olivierjuan/master
2 parents f26e47e + 6a788ac commit a9c19b6

24 files changed

+488
-72
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: Bug Report
2+
description: Create a report if something doesn't work quite right.
3+
labels: ["bug", "needs triage"]
4+
5+
body:
6+
- type: markdown
7+
attributes:
8+
value: |
9+
Thanks for taking the time to fill out this bug report!
10+
11+
- type: checkboxes
12+
id: checks
13+
attributes:
14+
label: Version Checks (indicate both or one)
15+
options:
16+
- label: >
17+
I have confirmed this bug exists on the lastest
18+
[release](https://github.com/pypsa/linopy/releases) of Linopy.
19+
- label: >
20+
I have confirmed this bug exists on the current
21+
[`master`](https://github.com/pypsa/linopy/tree/master) branch of Linopy.
22+
23+
- type: textarea
24+
id: problem
25+
attributes:
26+
label: Issue Description
27+
description: >
28+
Please provide a description of the issue.
29+
validations:
30+
required: true
31+
32+
- type: textarea
33+
id: example
34+
validations:
35+
required: true
36+
attributes:
37+
label: Reproducible Example
38+
description: >
39+
Please provide a minimal reproduciable example. See how to [craft minimal bug reports](https://matthewrocklin.com/minimal-bug-reports).
40+
placeholder: >
41+
from linopy import Model
42+
43+
m = Model()
44+
45+
render: python
46+
47+
- type: textarea
48+
id: expected-behavior
49+
validations:
50+
required: true
51+
attributes:
52+
label: Expected Behavior
53+
description: >
54+
Please describe or show a code example of the expected behavior.
55+
56+
- type: textarea
57+
id: version
58+
attributes:
59+
label: Installed Versions
60+
description: >
61+
Please share information on your environment. Paste the output below. For conda ``conda env export`` and for pip ``pip freeze``.
62+
value: >
63+
<details>
64+
65+
Replace this line.
66+
67+
</details>

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
blank_issues_enabled: true
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
name: Feature Request
3+
about: Suggest an idea for this project
4+
title: ''
5+
labels: ["feature", "needs triage"]
6+
assignees: ''
7+
8+
---
9+
10+
## Describe the feature you'd like to see
11+
12+
*Please give a clear and concise description and provide context why the feature would be useful.*
13+
*Also, we'd appreciate any implementation ideas and references you already have.*

.github/pull_request_template.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Closes # (if applicable).
2+
3+
## Changes proposed in this Pull Request
4+
5+
6+
## Checklist
7+
8+
- [ ] Code changes are sufficiently documented; i.e. new functions contain docstrings and further explanations may be given in `doc`.
9+
- [ ] Unit tests for new features were added (if applicable).
10+
- [ ] A note for the release notes `doc/release_notes.rst` of the upcoming release is included.
11+
- [ ] I consent to the release of this PR's code under the MIT license.

.github/workflows/test-models.yml

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
name: Test models
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
branches:
9+
- master
10+
schedule:
11+
- cron: "0 5 * * *"
12+
13+
# Cancel any in-progress runs when a new run is triggered
14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.ref }}
16+
cancel-in-progress: true
17+
18+
jobs:
19+
test-pypsa-eur:
20+
name: PyPSA-Eur
21+
runs-on: ubuntu-latest
22+
strategy:
23+
fail-fast: false
24+
matrix:
25+
version:
26+
- master
27+
# - latest # Activate when v0.14.0 is released
28+
29+
defaults:
30+
run:
31+
shell: bash -l {0}
32+
33+
steps:
34+
- uses: actions/checkout@v4
35+
with:
36+
repository: PyPSA/pypsa-eur
37+
ref: master
38+
39+
- name: Check out latest release
40+
if: matrix.version == 'latest'
41+
run: |
42+
git fetch --tags
43+
latest_tag=$(git describe --tags `git rev-list --tags --max-count=1`)
44+
git checkout $latest_tag
45+
46+
# Only run check if package is not pinned
47+
- name: Check if inhouse package is pinned
48+
run: |
49+
grep_line=$(grep -- '- pypsa' envs/environment.yaml)
50+
if [[ $grep_line == *"<"* || $grep_line == *"=="* ]]; then
51+
echo "pinned=true" >> $GITHUB_ENV
52+
else
53+
echo "pinned=false" >> $GITHUB_ENV
54+
fi
55+
56+
- name: Setup secrets & cache dates
57+
if: env.pinned == 'false'
58+
run: |
59+
echo -ne "url: ${CDSAPI_URL}\nkey: ${CDSAPI_TOKEN}\n" > ~/.cdsapirc
60+
echo "week=$(date +'%Y%U')" >> $GITHUB_ENV # data and cutouts
61+
62+
- uses: actions/cache@v4
63+
if: env.pinned == 'false'
64+
with:
65+
path: |
66+
data
67+
cutouts
68+
key: data-cutouts-${{ env.week }}
69+
70+
- uses: conda-incubator/setup-miniconda@v3
71+
if: env.pinned == 'false'
72+
with:
73+
activate-environment: pypsa-eur
74+
75+
- name: Cache Conda env
76+
if: env.pinned == 'false'
77+
uses: actions/cache@v4
78+
with:
79+
path: ${{ env.CONDA }}/envs
80+
key: conda-pypsa-eur-${{ env.week }}-${{ hashFiles('envs/linux-pinned.yaml') }}
81+
id: cache-env
82+
83+
- name: Update environment
84+
if: env.pinned == 'false' && steps.cache-env.outputs.cache-hit != 'true'
85+
run: conda env update -n pypsa-eur -f envs/linux-pinned.yaml
86+
87+
- name: Install package from ref
88+
if: env.pinned == 'false'
89+
run: |
90+
python -m pip install git+https://github.com/${{ github.repository }}@${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
91+
92+
- name: Run snakemake test workflows
93+
if: env.pinned == 'false'
94+
run: |
95+
make test
96+
97+
- name: Run unit tests
98+
if: env.pinned == 'false'
99+
run: |
100+
make unit-test
101+
102+
- name: Upload artifacts
103+
if: env.pinned == 'false'
104+
uses: actions/upload-artifact@v4
105+
with:
106+
name: results-pypsa-eur-${{ matrix.version }}
107+
path: |
108+
logs
109+
.snakemake/log
110+
results
111+
retention-days: 3

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ Highs.log
1717
paper/
1818
monkeytype.sqlite3
1919

20+
# Environments
21+
.env
22+
.venv
23+
env/
24+
venv/
25+
ENV/
26+
env.bak/
27+
venv.bak/
2028

2129
benchmark/*.pdf
2230
benchmark/benchmarks

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ repos:
99
- id: trailing-whitespace
1010
- id: check-merge-conflict
1111
- repo: https://github.com/astral-sh/ruff-pre-commit
12-
rev: v0.8.1
12+
rev: v0.9.4
1313
hooks:
1414
- id: ruff
1515
args: [--fix]
@@ -25,7 +25,7 @@ repos:
2525
hooks:
2626
- id: blackdoc
2727
- repo: https://github.com/codespell-project/codespell
28-
rev: v2.3.0
28+
rev: v2.4.1
2929
hooks:
3030
- id: codespell
3131
types_or: [python, rst, markdown]

doc/release_notes.rst

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,35 @@
11
Release Notes
22
=============
33

4-
.. Upcoming Version
5-
.. ----------------
4+
Upcoming Version
5+
----------------
6+
7+
* The internal handling of `Solution` objects was improved for more consistency. Solution objects created from solver calls now preserve the exact index names from the input file.
8+
* Multiplication of a linear expression by a constant value may now introduce new dimensions.
9+
* Added method `unstack` to `LinearExpression`, `Variable` and `Constraint` to unstack a dimension.
10+
11+
Version 0.4.4
12+
--------------
13+
14+
* **IMPORTANT BUGFIX**: The last slice of constraints was not correctly written to LP files in case the constraint size was not a multiple of the slice size. This is fixed now.
15+
* Solution files that following a different naming scheme of variables and constraints using more than on initial letter in the prefix (e.g. `col123`, `row456`) are now supported.
16+
* GLPK solver is always called with the `--freemps` option instead of the `--mps` when using the Solver API to solve an external MPS file. `--mps` is for the older fixed-column MPS format that is rarely used nowadays. Almost all fixed MPS files can be parsed by the free MPS format.
617

718
* Added extra argument in io methods `explicit_coordinate_names` to allow for export of
819
variables and constraints with explicit coordinate names.
920

1021
Version 0.4.3
1122
--------------
1223

13-
* When creating slices for variables and constraints (important for the `solve` function), the slicing is now fixed in case now dimension to slice is available.
24+
* **Version 0.4.3 includes a major bug and can not be installed anymore.**
25+
* When creating slices for variables and constraints (important for the `solve` function), the slicing is now fixed in case no dimension to slice is available.
1426
* Added a pandas priority attribute. With this change, the operation with pandas objects is now prioritizing linopy objects over pandas objects. This is useful when the using linopy objects in arithmetic operations with pandas objects, e.g. `a * x` where `a` is a pandas Series/DataFrame and `x` is a linopy variable.
1527
* The method :meth:`model.to_file <linopy.model.Model.to_file>` now includes a progress argument to enable or disable the progress bar while writing.
1628

1729
Version 0.4.2
1830
--------------
1931

32+
* **Version 0.4.2 includes a major bug and can not be installed anymore.**
2033
* Fix the file handler to properly close the file when reading the sense from a problem file.
2134

2235
Version 0.4.1

examples/manipulating-models.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
"\n",
6565
"## Varying lower and upper bounds\n",
6666
"\n",
67-
"Now, let's say we want to set the lower bound of `y(t)` to 1. This would translate to:"
67+
"Now, let's say we want to set the lower bound of `x(t)` to 1. This would translate to:"
6868
]
6969
},
7070
{
@@ -178,7 +178,7 @@
178178
"metadata": {},
179179
"source": [
180180
".. note::\n",
181-
" The same could have been achieved by calling `m.variables.con1.rhs = 8 * factor`\n",
181+
" The same could have been achieved by calling `m.constraints.con1.rhs = 8 * factor`\n",
182182
"\n",
183183
"Let's solve it again!"
184184
]

linopy/common.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@
3838
from linopy.variables import Variable
3939

4040

41+
def set_int_index(series: pd.Series) -> pd.Series:
42+
"""
43+
Convert string index to int index.
44+
"""
45+
if not series.empty and not pd.api.types.is_integer_dtype(series.index):
46+
cutoff = count_initial_letters(str(series.index[0]))
47+
series.index = series.index.str[cutoff:].astype(int)
48+
return series
49+
50+
4151
def maybe_replace_sign(sign: str) -> str:
4252
"""
4353
Replace the sign with an alternative sign if available.
@@ -519,7 +529,7 @@ def iterate_slices(
519529
return
520530

521531
# number of slices
522-
n_slices = max(size // slice_size, 1)
532+
n_slices = max((size + slice_size - 1) // slice_size, 1)
523533

524534
# leading dimension (the dimension with the largest size)
525535
sizes = {dim: ds.sizes[dim] for dim in slice_dims}
@@ -533,12 +543,12 @@ def iterate_slices(
533543
if size_of_leading_dim < n_slices:
534544
n_slices = size_of_leading_dim
535545

536-
chunk_size = ds.sizes[leading_dim] // n_slices
546+
chunk_size = (ds.sizes[leading_dim] + n_slices - 1) // n_slices
537547

538548
# Iterate over the Cartesian product of slice indices
539549
for i in range(n_slices):
540550
start = i * chunk_size
541-
end = start + chunk_size
551+
end = min(start + chunk_size, size_of_leading_dim)
542552
slice_dict = {leading_dim: slice(start, end)}
543553
yield ds.isel(slice_dict)
544554

@@ -547,6 +557,19 @@ def _remap(array, mapping):
547557
return mapping[array.ravel()].reshape(array.shape)
548558

549559

560+
def count_initial_letters(word: str):
561+
"""
562+
Count the number of initial letters in a word.
563+
"""
564+
count = 0
565+
for char in word:
566+
if char.isalpha():
567+
count += 1
568+
else:
569+
break
570+
return count
571+
572+
550573
def replace_by_map(ds, mapping):
551574
"""
552575
Replace values in a DataArray by a one-dimensional mapping.

0 commit comments

Comments
 (0)