primalsolver 0.1.0__tar.gz → 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {primalsolver-0.1.0/src/primalsolver.egg-info → primalsolver-0.1.2}/PKG-INFO +8 -7
- {primalsolver-0.1.0 → primalsolver-0.1.2}/README.md +8 -7
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/mpsio.c +143 -16
- {primalsolver-0.1.0 → primalsolver-0.1.2}/pyproject.toml +4 -1
- {primalsolver-0.1.0 → primalsolver-0.1.2}/setup.py +6 -2
- {primalsolver-0.1.0 → primalsolver-0.1.2}/src/primalsolver/__init__.py +1 -1
- {primalsolver-0.1.0 → primalsolver-0.1.2/src/primalsolver.egg-info}/PKG-INFO +8 -7
- {primalsolver-0.1.0 → primalsolver-0.1.2}/LICENSE +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/MANIFEST.in +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/cbf.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/cbf.h +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/expcone.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/expcone.h +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/ipm.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/ipm.h +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/linalg.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/linalg.h +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/mpsio.h +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/presolve.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/presolve.h +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal.h +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_afe.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_bar.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_conicopt.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_core.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_djc.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_get.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_info.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_meta.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_mip.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_mip_opt.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_misc.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_names.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_optimize.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_priv.h +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_put.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_qcon.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_quad.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_sdptask.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_solio.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_solution.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_std.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_vartype.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/primal_verdict.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/scaling.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/scaling.h +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/sdp.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/sdp.h +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/simplex.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/simplex.h +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/socp.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/socp.h +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/stdform.c +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/csrc/stdform.h +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/setup.cfg +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/src/primalsolver/_bindings.py +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/src/primalsolver/_native.py +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/src/primalsolver.egg-info/SOURCES.txt +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/src/primalsolver.egg-info/dependency_links.txt +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/src/primalsolver.egg-info/top_level.txt +0 -0
- {primalsolver-0.1.0 → primalsolver-0.1.2}/sync_csrc.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: primalsolver
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: PrimalSolver: dependency-free convex optimization (LP/QP/SOCP/SDP/exp-power/MIP) in C99, callable from Python via ctypes.
|
|
5
5
|
Author: Gaetano Minardi
|
|
6
6
|
License: Apache-2.0
|
|
@@ -208,13 +208,14 @@ Notes:
|
|
|
208
208
|
git push origin main --tags
|
|
209
209
|
```
|
|
210
210
|
|
|
211
|
-
6. **Publish**: a
|
|
212
|
-
builds the wheels + sdist and uploads
|
|
213
|
-
runs
|
|
214
|
-
|
|
211
|
+
6. **Publish**: pushing a **tag** `vX.Y.Z` triggers
|
|
212
|
+
`.github/workflows/wheels.yml`, which builds the wheels + sdist and uploads
|
|
213
|
+
them as artifacts. Publishing to PyPI runs when a **GitHub Release** is
|
|
214
|
+
published for that tag, using the repository secret `PYPI_API_TOKEN` (token
|
|
215
|
+
auth).
|
|
215
216
|
|
|
216
|
-
Prerequisites (one-time):
|
|
217
|
-
|
|
217
|
+
Prerequisites (one-time): add `PYPI_API_TOKEN` in the repo (Settings →
|
|
218
|
+
Secrets and variables → Actions) with the PyPI API token.
|
|
218
219
|
|
|
219
220
|
Manual/fallback publish (e.g. TestPyPI):
|
|
220
221
|
|
|
@@ -191,13 +191,14 @@ Notes:
|
|
|
191
191
|
git push origin main --tags
|
|
192
192
|
```
|
|
193
193
|
|
|
194
|
-
6. **Publish**: a
|
|
195
|
-
builds the wheels + sdist and uploads
|
|
196
|
-
runs
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
194
|
+
6. **Publish**: pushing a **tag** `vX.Y.Z` triggers
|
|
195
|
+
`.github/workflows/wheels.yml`, which builds the wheels + sdist and uploads
|
|
196
|
+
them as artifacts. Publishing to PyPI runs when a **GitHub Release** is
|
|
197
|
+
published for that tag, using the repository secret `PYPI_API_TOKEN` (token
|
|
198
|
+
auth).
|
|
199
|
+
|
|
200
|
+
Prerequisites (one-time): add `PYPI_API_TOKEN` in the repo (Settings →
|
|
201
|
+
Secrets and variables → Actions) with the PyPI API token.
|
|
201
202
|
|
|
202
203
|
Manual/fallback publish (e.g. TestPyPI):
|
|
203
204
|
|
|
@@ -83,21 +83,73 @@ static void toks_push(Toks *T, const char *s, int len) {
|
|
|
83
83
|
* Grammar: a '*' is a comment only in column 1 (MPS), a '\' truncates the rest
|
|
84
84
|
* of the line even inside MPS text (LP), and the characters : < > = are tokens
|
|
85
85
|
* of their own -- with a following '=' glued to them -- so a reader can
|
|
86
|
-
* recognise a relation without looking ahead.
|
|
87
|
-
|
|
86
|
+
* recognise a relation without looking ahead. `lp` selects the LP scan, where
|
|
87
|
+
* '+' '-' '*' are separators too and a number is scanned whole, so a term may
|
|
88
|
+
* be written "x0+x1", "2x0" or "2*x" with no spaces; the MPS scan keeps
|
|
89
|
+
* whitespace-delimited names, where those characters may be part of a name. */
|
|
90
|
+
static int toks_load(const char *filename, Toks *T, int lp) {
|
|
88
91
|
FILE *f = fopen(filename, "r");
|
|
89
92
|
if (!f) return 0;
|
|
90
93
|
char line[8192];
|
|
91
94
|
while (fgets(line, sizeof line, f)) {
|
|
92
95
|
T->curline++;
|
|
93
|
-
|
|
94
|
-
if (*p == '*') continue; /* MPS comment line */
|
|
96
|
+
if (line[0] == '*') continue; /* comment line (MPS; also seen in LP) */
|
|
95
97
|
char *q = strchr(line, '\\');
|
|
96
98
|
if (q) *q = 0; /* LP comment */
|
|
97
|
-
p = line;
|
|
99
|
+
char *p = line;
|
|
100
|
+
if (lp) {
|
|
101
|
+
/* LP grammar: '+' '-' '*' are separators and are routinely glued
|
|
102
|
+
* to what they act on ("x0+x1", "2x0", "-x", "2*x"), so a name
|
|
103
|
+
* never contains one, a sign is always its own token, and a number
|
|
104
|
+
* is scanned whole (exponent sign included). Without this every
|
|
105
|
+
* glued term coined a variable named after the whole text -- the
|
|
106
|
+
* phantom "+COLONE" of issue #27 was the sign case. */
|
|
107
|
+
while (*p) {
|
|
108
|
+
while (*p && isspace((unsigned char)*p)) p++;
|
|
109
|
+
if (!*p) break;
|
|
110
|
+
unsigned char ch = (unsigned char)*p;
|
|
111
|
+
if (ch == ':' || ch == '[' || ch == ']') {
|
|
112
|
+
toks_push(T, p, 1); p++; continue;
|
|
113
|
+
}
|
|
114
|
+
if (ch == '<' || ch == '>' || ch == '=') {
|
|
115
|
+
int len = (p[1] == '=') ? 2 : 1;
|
|
116
|
+
toks_push(T, p, len); p += len; continue;
|
|
117
|
+
}
|
|
118
|
+
if (ch == '+' || ch == '-') { toks_push(T, p, 1); p++; continue; }
|
|
119
|
+
if (ch == '*') { p++; continue; } /* multiplication: a separator */
|
|
120
|
+
if (isdigit(ch) || (ch == '.' && isdigit((unsigned char)p[1]))) {
|
|
121
|
+
char *st = p;
|
|
122
|
+
while (isdigit((unsigned char)*p)) p++;
|
|
123
|
+
if (*p == '.') { p++; while (isdigit((unsigned char)*p)) p++; }
|
|
124
|
+
if (*p == 'e' || *p == 'E') {
|
|
125
|
+
char *sv = p; p++;
|
|
126
|
+
if (*p == '+' || *p == '-') p++;
|
|
127
|
+
if (isdigit((unsigned char)*p))
|
|
128
|
+
while (isdigit((unsigned char)*p)) p++;
|
|
129
|
+
else p = sv; /* not an exponent after all */
|
|
130
|
+
}
|
|
131
|
+
toks_push(T, st, (int)(p - st)); continue;
|
|
132
|
+
}
|
|
133
|
+
if (isalpha(ch) || ch == '_') {
|
|
134
|
+
char *st = p; p++;
|
|
135
|
+
while (isalnum((unsigned char)*p) || *p == '_' || *p == '.' ||
|
|
136
|
+
*p == '$' || *p == '~') p++;
|
|
137
|
+
toks_push(T, st, (int)(p - st)); continue;
|
|
138
|
+
}
|
|
139
|
+
toks_push(T, p, 1); p++; /* unknown char: never stall */
|
|
140
|
+
}
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
/* MPS / free MPS: names are whitespace-delimited with no operators, so a
|
|
144
|
+
* sign glued to a name ("+COLONE") is two tokens while a sign glued to a
|
|
145
|
+
* number ("-3.5") stays one (parse_num consumes it whole). */
|
|
98
146
|
while (*p) {
|
|
99
147
|
while (*p && isspace((unsigned char)*p)) p++;
|
|
100
148
|
if (!*p) break;
|
|
149
|
+
if ((*p == '+' || *p == '-') &&
|
|
150
|
+
(isalpha((unsigned char)p[1]) || p[1] == '_')) {
|
|
151
|
+
toks_push(T, p, 1); p++; continue;
|
|
152
|
+
}
|
|
101
153
|
if (*p == ':') { toks_push(T, p, 1); p++; continue; }
|
|
102
154
|
if (*p == '<' || *p == '>') {
|
|
103
155
|
int len = (p[1] == '=') ? 2 : 1;
|
|
@@ -178,6 +230,19 @@ static char rel_char(const char *s) {
|
|
|
178
230
|
return 'E';
|
|
179
231
|
}
|
|
180
232
|
|
|
233
|
+
/* True if token i may continue the statement whose previous token is i-1. An
|
|
234
|
+
* LP expression wraps onto the next line only when a '+'/'-' sits at one end of
|
|
235
|
+
* the break; without that a new line is a new statement. This is what tells
|
|
236
|
+
* "x <= 4\n x0 <= 3" (two constraints) apart from "x <=\n 4 + y" (one wrapped
|
|
237
|
+
* constraint), and it is why the readers know where a constraint ends. */
|
|
238
|
+
static int lp_cont(const Toks *T, int i) {
|
|
239
|
+
if (i <= 0) return 1;
|
|
240
|
+
if (T->line[i] == T->line[i - 1]) return 1;
|
|
241
|
+
if (ieq(T->tok[i], "+") || ieq(T->tok[i], "-")) return 1;
|
|
242
|
+
if (ieq(T->tok[i - 1], "+") || ieq(T->tok[i - 1], "-")) return 1;
|
|
243
|
+
return 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
181
246
|
/* Same out-of-memory rule as toks_push: allocate or exit. */
|
|
182
247
|
static char *xstrdup(const char *s) {
|
|
183
248
|
size_t n = strlen(s) + 1;
|
|
@@ -267,6 +332,15 @@ static int parse_num(const char *s, double *v) {
|
|
|
267
332
|
return 1;
|
|
268
333
|
}
|
|
269
334
|
|
|
335
|
+
/* An LP bound may be written "inf"/"infinity" (optionally signed) instead of a
|
|
336
|
+
* number; the LP scanner emits the sign separately, so this is never asked for
|
|
337
|
+
* "-inf" as one token. Recognising it keeps "x <= +inf" from coining a
|
|
338
|
+
* variable named "+inf". */
|
|
339
|
+
static int parse_inf(const char *s, double *v) {
|
|
340
|
+
if (ieq(s, "inf") || ieq(s, "infinity")) { *v = INF; return 1; }
|
|
341
|
+
return 0;
|
|
342
|
+
}
|
|
343
|
+
|
|
270
344
|
/* ================= MPS writer ================= */
|
|
271
345
|
/* The writer names rows and columns as the TASK calls them when the name is
|
|
272
346
|
* non-empty and free of whitespace (MPS/LP names are whitespace-delimited), and
|
|
@@ -1169,7 +1243,9 @@ static PRIMALrescodee lp_read(PRIMALtask_t t, const Toks *T) {
|
|
|
1169
1243
|
double v;
|
|
1170
1244
|
if (parse_num(tk, &v)) {
|
|
1171
1245
|
if (i + 1 < T->n && !is_lp_section(T->tok[i + 1]) &&
|
|
1172
|
-
!tok_is(T, i + 1, ":"))
|
|
1246
|
+
!tok_is(T, i + 1, ":") && !ieq(T->tok[i + 1], "+") &&
|
|
1247
|
+
!ieq(T->tok[i + 1], "-") &&
|
|
1248
|
+
T->line[i + 1] == T->line[i]) {
|
|
1173
1249
|
int c = names_get(&cols, T->tok[i + 1], 1);
|
|
1174
1250
|
ENSURE_CJ(c);
|
|
1175
1251
|
cj[c] += sign * v;
|
|
@@ -1213,9 +1289,11 @@ static PRIMALrescodee lp_read(PRIMALtask_t t, const Toks *T) {
|
|
|
1213
1289
|
}
|
|
1214
1290
|
i += 2;
|
|
1215
1291
|
}
|
|
1292
|
+
int cstart = i;
|
|
1216
1293
|
double lsign = 1.0, lconst = 0.0;
|
|
1217
1294
|
while (i < T->n && !is_rel(T->tok[i]) && !is_lp_section(T->tok[i]) &&
|
|
1218
|
-
!tok_is(T, i, ":") && !tok_is(T, i + 1, ":")
|
|
1295
|
+
!tok_is(T, i, ":") && !tok_is(T, i + 1, ":") &&
|
|
1296
|
+
(i == cstart || lp_cont(T, i))) {
|
|
1219
1297
|
if (T->tok[i][0] == '[' || T->tok[i][0] == ']') { rc = PRIMAL_RES_ERR_FILE; goto done; }
|
|
1220
1298
|
if (ieq(T->tok[i], "+")) { lsign = 1.0; i++; continue; }
|
|
1221
1299
|
if (ieq(T->tok[i], "-")) { lsign = -1.0; i++; continue; }
|
|
@@ -1223,7 +1301,8 @@ static PRIMALrescodee lp_read(PRIMALtask_t t, const Toks *T) {
|
|
|
1223
1301
|
if (parse_num(T->tok[i], &v)) {
|
|
1224
1302
|
if (i + 2 < T->n && !is_rel(T->tok[i + 1]) &&
|
|
1225
1303
|
!is_lp_section(T->tok[i + 1]) && !tok_is(T, i + 1, ":") &&
|
|
1226
|
-
!tok_is(T, i + 2, ":"))
|
|
1304
|
+
!tok_is(T, i + 2, ":") && !ieq(T->tok[i + 1], "+") &&
|
|
1305
|
+
!ieq(T->tok[i + 1], "-") && T->line[i + 1] == T->line[i]) {
|
|
1227
1306
|
int c = names_get(&cols, T->tok[i + 1], 1);
|
|
1228
1307
|
ipush(&C->sub, c); dpush(&C->val, lsign * v);
|
|
1229
1308
|
i += 2;
|
|
@@ -1237,9 +1316,11 @@ static PRIMALrescodee lp_read(PRIMALtask_t t, const Toks *T) {
|
|
|
1237
1316
|
}
|
|
1238
1317
|
if (i >= T->n || !is_rel(T->tok[i])) { rc = PRIMAL_RES_ERR_FILE; goto done; }
|
|
1239
1318
|
char r2 = rel_char(T->tok[i++]);
|
|
1319
|
+
int rstart = i;
|
|
1240
1320
|
double rsign = 1.0, rconst = 0.0;
|
|
1241
1321
|
while (i < T->n && !is_lp_section(T->tok[i]) && !is_rel(T->tok[i]) &&
|
|
1242
|
-
!tok_is(T, i, ":") && !tok_is(T, i + 1, ":")
|
|
1322
|
+
!tok_is(T, i, ":") && !tok_is(T, i + 1, ":") &&
|
|
1323
|
+
(i == rstart || lp_cont(T, i))) {
|
|
1243
1324
|
if (T->tok[i][0] == '[' || T->tok[i][0] == ']') { rc = PRIMAL_RES_ERR_FILE; goto done; }
|
|
1244
1325
|
if (ieq(T->tok[i], "+")) { rsign = 1.0; i++; continue; }
|
|
1245
1326
|
if (ieq(T->tok[i], "-")) { rsign = -1.0; i++; continue; }
|
|
@@ -1247,7 +1328,8 @@ static PRIMALrescodee lp_read(PRIMALtask_t t, const Toks *T) {
|
|
|
1247
1328
|
if (parse_num(T->tok[i], &v)) {
|
|
1248
1329
|
if (i + 2 < T->n && !is_lp_section(T->tok[i + 1]) &&
|
|
1249
1330
|
!is_rel(T->tok[i + 1]) && !tok_is(T, i + 1, ":") &&
|
|
1250
|
-
!tok_is(T, i + 2, ":"))
|
|
1331
|
+
!tok_is(T, i + 2, ":") && !ieq(T->tok[i + 1], "+") &&
|
|
1332
|
+
!ieq(T->tok[i + 1], "-") && T->line[i + 1] == T->line[i]) {
|
|
1251
1333
|
/* coef var on the right: move left negated */
|
|
1252
1334
|
int c = names_get(&cols, T->tok[i + 1], 1);
|
|
1253
1335
|
ipush(&C->sub, c); dpush(&C->val, -rsign * v);
|
|
@@ -1263,6 +1345,50 @@ static PRIMALrescodee lp_read(PRIMALtask_t t, const Toks *T) {
|
|
|
1263
1345
|
C->rel = r2;
|
|
1264
1346
|
C->rhs = rconst - lconst;
|
|
1265
1347
|
ncon++;
|
|
1348
|
+
/* A ranged row "l rel1 expr rel2 r": the row above is "l - expr
|
|
1349
|
+
* rel1 0"; the second relation makes "expr rel2 r" a row of its
|
|
1350
|
+
* own, so the other bound is not silently dropped. The middle
|
|
1351
|
+
* expression is -(C), since C holds l - expr. */
|
|
1352
|
+
if (i < T->n && is_rel(T->tok[i])) {
|
|
1353
|
+
if (ncon == con_cap) {
|
|
1354
|
+
con_cap = con_cap ? con_cap * 2 : 8;
|
|
1355
|
+
LpCon *nc2 = (LpCon *)realloc(cons, (size_t)con_cap * sizeof(LpCon));
|
|
1356
|
+
if (!nc2) { rc = PRIMAL_RES_ERR_ALLOC; goto done; }
|
|
1357
|
+
cons = nc2;
|
|
1358
|
+
}
|
|
1359
|
+
C = &cons[ncon - 1]; /* realloc may have moved the array */
|
|
1360
|
+
LpCon *D = &cons[ncon];
|
|
1361
|
+
memset(D, 0, sizeof *D);
|
|
1362
|
+
D->rel = rel_char(T->tok[i++]);
|
|
1363
|
+
for (int z = 0; z < C->sub.n; z++) {
|
|
1364
|
+
ipush(&D->sub, C->sub.a[z]);
|
|
1365
|
+
dpush(&D->val, -C->val.a[z]);
|
|
1366
|
+
}
|
|
1367
|
+
double dsign = 1.0, dconst = 0.0;
|
|
1368
|
+
while (i < T->n && !is_lp_section(T->tok[i]) && !is_rel(T->tok[i]) &&
|
|
1369
|
+
!tok_is(T, i, ":") && !tok_is(T, i + 1, ":")) {
|
|
1370
|
+
if (ieq(T->tok[i], "+")) { dsign = 1.0; i++; continue; }
|
|
1371
|
+
if (ieq(T->tok[i], "-")) { dsign = -1.0; i++; continue; }
|
|
1372
|
+
double v2;
|
|
1373
|
+
if (parse_num(T->tok[i], &v2) || parse_inf(T->tok[i], &v2)) {
|
|
1374
|
+
if (i + 2 < T->n && !is_rel(T->tok[i + 1]) &&
|
|
1375
|
+
!is_lp_section(T->tok[i + 1]) && !tok_is(T, i + 1, ":") &&
|
|
1376
|
+
!tok_is(T, i + 2, ":") && !ieq(T->tok[i + 1], "+") &&
|
|
1377
|
+
!ieq(T->tok[i + 1], "-") && T->line[i + 1] == T->line[i]) {
|
|
1378
|
+
int c = names_get(&cols, T->tok[i + 1], 1);
|
|
1379
|
+
ipush(&D->sub, c); dpush(&D->val, -dsign * v2);
|
|
1380
|
+
i += 2;
|
|
1381
|
+
} else { dconst += dsign * v2; i++; }
|
|
1382
|
+
} else {
|
|
1383
|
+
int c = names_get(&cols, T->tok[i], 1);
|
|
1384
|
+
ipush(&D->sub, c); dpush(&D->val, -dsign);
|
|
1385
|
+
i++;
|
|
1386
|
+
}
|
|
1387
|
+
dsign = 1.0;
|
|
1388
|
+
}
|
|
1389
|
+
D->rhs = dconst;
|
|
1390
|
+
ncon++;
|
|
1391
|
+
}
|
|
1266
1392
|
}
|
|
1267
1393
|
continue;
|
|
1268
1394
|
}
|
|
@@ -1274,7 +1400,7 @@ static PRIMALrescodee lp_read(PRIMALtask_t t, const Toks *T) {
|
|
|
1274
1400
|
if (ieq(T->tok[k], "-")) { sgn = -1.0; k++; }
|
|
1275
1401
|
else if (ieq(T->tok[k], "+")) k++;
|
|
1276
1402
|
double v;
|
|
1277
|
-
if (parse_num(T->tok[k], &v)) {
|
|
1403
|
+
if (parse_num(T->tok[k], &v) || parse_inf(T->tok[k], &v)) {
|
|
1278
1404
|
v *= sgn;
|
|
1279
1405
|
i = k + 1;
|
|
1280
1406
|
if (i >= T->n || !is_rel(T->tok[i])) { rc = PRIMAL_RES_ERR_FILE; goto done; }
|
|
@@ -1289,7 +1415,7 @@ static PRIMALrescodee lp_read(PRIMALtask_t t, const Toks *T) {
|
|
|
1289
1415
|
if (ieq(T->tok[i], "-")) { s2 = -1.0; i++; }
|
|
1290
1416
|
else if (ieq(T->tok[i], "+")) i++;
|
|
1291
1417
|
double v2;
|
|
1292
|
-
if (i >= T->n || !parse_num(T->tok[i], &v2)) { rc = PRIMAL_RES_ERR_FILE; goto done; }
|
|
1418
|
+
if (i >= T->n || (!parse_num(T->tok[i], &v2) && !parse_inf(T->tok[i], &v2))) { rc = PRIMAL_RES_ERR_FILE; goto done; }
|
|
1293
1419
|
upfin[c] = 1; bup[c] = v2 * s2;
|
|
1294
1420
|
i++;
|
|
1295
1421
|
}
|
|
@@ -1306,7 +1432,7 @@ static PRIMALrescodee lp_read(PRIMALtask_t t, const Toks *T) {
|
|
|
1306
1432
|
if (ieq(T->tok[i], "-")) { s2 = -1.0; i++; }
|
|
1307
1433
|
else if (ieq(T->tok[i], "+")) i++;
|
|
1308
1434
|
double v2;
|
|
1309
|
-
if (i >= T->n || !parse_num(T->tok[i], &v2)) { rc = PRIMAL_RES_ERR_FILE; goto done; }
|
|
1435
|
+
if (i >= T->n || (!parse_num(T->tok[i], &v2) && !parse_inf(T->tok[i], &v2))) { rc = PRIMAL_RES_ERR_FILE; goto done; }
|
|
1310
1436
|
v2 *= s2;
|
|
1311
1437
|
if (rc2 == 'L') { upfin[c] = 1; bup[c] = v2; }
|
|
1312
1438
|
else if (rc2 == 'G') { lofin[c] = 1; blo[c] = v2; }
|
|
@@ -2171,8 +2297,9 @@ PRIMALrescodee primalio_read(PRIMALtask_t t, const char *filename) {
|
|
|
2171
2297
|
return rc;
|
|
2172
2298
|
}
|
|
2173
2299
|
Toks T = {0};
|
|
2174
|
-
|
|
2175
|
-
|
|
2300
|
+
int islp = has_ext(filename, ".lp");
|
|
2301
|
+
if (!toks_load(filename, &T, islp)) return PRIMAL_RES_ERR_FILE;
|
|
2302
|
+
PRIMALrescodee rc = islp ? lp_read(t, &T) : mps_read(t, &T);
|
|
2176
2303
|
toks_free(&T);
|
|
2177
2304
|
return rc;
|
|
2178
2305
|
}
|
|
@@ -2217,7 +2344,7 @@ PRIMALrescodee primalio_read_format(PRIMALtask_t t, const char *filename, int fo
|
|
|
2217
2344
|
if (format == PRIMAL_DATA_FORMAT_MPS || format == PRIMAL_DATA_FORMAT_LP ||
|
|
2218
2345
|
format == PRIMAL_DATA_FORMAT_FREE_MPS) {
|
|
2219
2346
|
Toks T = {0};
|
|
2220
|
-
if (!toks_load(filename, &T)) return PRIMAL_RES_ERR_FILE;
|
|
2347
|
+
if (!toks_load(filename, &T, format == PRIMAL_DATA_FORMAT_LP)) return PRIMAL_RES_ERR_FILE;
|
|
2221
2348
|
PRIMALrescodee rc = (format == PRIMAL_DATA_FORMAT_LP) ? lp_read(t, &T) : mps_read(t, &T);
|
|
2222
2349
|
toks_free(&T);
|
|
2223
2350
|
return rc;
|
|
@@ -43,7 +43,10 @@ test-command = "python -c \"import primalsolver as ps; print(ps.version())\""
|
|
|
43
43
|
# csrc/ is vendored in the repo (run tools/../sync_csrc.py before tagging).
|
|
44
44
|
|
|
45
45
|
[tool.cibuildwheel.linux]
|
|
46
|
-
|
|
46
|
+
# x86_64 only: aarch64 requires QEMU emulation on an x86_64 runner, which is
|
|
47
|
+
# very slow (or hangs) building the C library. Enable it with a native arm64
|
|
48
|
+
# runner (runs-on: ubuntu-24.04-arm) if needed.
|
|
49
|
+
archs = ["x86_64"]
|
|
47
50
|
|
|
48
51
|
[tool.cibuildwheel.macos]
|
|
49
52
|
archs = ["x86_64", "arm64"]
|
|
@@ -63,8 +63,12 @@ def _build_shared(dest):
|
|
|
63
63
|
base = cc + ["-std=c99", "-O2", "-fPIC", "-pthread", "-I", srcdir]
|
|
64
64
|
if sys.platform == "darwin":
|
|
65
65
|
out = os.path.join(dest, "libprimal.dylib")
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
# Build a universal2 dylib by default: cibuildwheel builds the x86_64 and
|
|
67
|
+
# arm64 wheels on one runner, and each needs its slice present in the
|
|
68
|
+
# library (delocate --require-archs). Skip if CC already sets -arch.
|
|
69
|
+
arch = [] if "-arch" in cc else ["-arch", "arm64", "-arch", "x86_64"]
|
|
70
|
+
cmd = base + arch + ["-dynamiclib", "-install_name", "@rpath/libprimal.dylib",
|
|
71
|
+
"-o", out, *srcs, "-lm"]
|
|
68
72
|
elif sys.platform.startswith("linux"):
|
|
69
73
|
out = os.path.join(dest, "libprimal.so")
|
|
70
74
|
cmd = base + ["-shared", "-Wl,-soname,libprimal.so", "-o", out, *srcs, "-lm"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: primalsolver
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: PrimalSolver: dependency-free convex optimization (LP/QP/SOCP/SDP/exp-power/MIP) in C99, callable from Python via ctypes.
|
|
5
5
|
Author: Gaetano Minardi
|
|
6
6
|
License: Apache-2.0
|
|
@@ -208,13 +208,14 @@ Notes:
|
|
|
208
208
|
git push origin main --tags
|
|
209
209
|
```
|
|
210
210
|
|
|
211
|
-
6. **Publish**: a
|
|
212
|
-
builds the wheels + sdist and uploads
|
|
213
|
-
runs
|
|
214
|
-
|
|
211
|
+
6. **Publish**: pushing a **tag** `vX.Y.Z` triggers
|
|
212
|
+
`.github/workflows/wheels.yml`, which builds the wheels + sdist and uploads
|
|
213
|
+
them as artifacts. Publishing to PyPI runs when a **GitHub Release** is
|
|
214
|
+
published for that tag, using the repository secret `PYPI_API_TOKEN` (token
|
|
215
|
+
auth).
|
|
215
216
|
|
|
216
|
-
Prerequisites (one-time):
|
|
217
|
-
|
|
217
|
+
Prerequisites (one-time): add `PYPI_API_TOKEN` in the repo (Settings →
|
|
218
|
+
Secrets and variables → Actions) with the PyPI API token.
|
|
218
219
|
|
|
219
220
|
Manual/fallback publish (e.g. TestPyPI):
|
|
220
221
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|