-
Notifications
You must be signed in to change notification settings - Fork 69
Fix -0.0 handling in lp writer #544
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
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea of a signed_number function, but i would let it directly act on expressions and return a simple expression.
This expression could then be a simple case statement.
| def signed_number_expr(col_name: str) -> list[pl.Expr]: | ||
| """ | ||
| Return polars expressions for a signed number string, handling -0.0 correctly. | ||
| This function returns two expressions: a sign prefix ('+' or '') and the | ||
| number cast to string. It normalizes -0.0 to +0.0 to avoid producing invalid | ||
| LP file syntax like "+-0.0" (which occurs because -0.0 >= 0 is True but | ||
| str(-0.0) is "-0.0"). | ||
| Parameters | ||
| ---------- | ||
| col_name : str | ||
| Name of the column containing the numeric value. | ||
| Returns | ||
| ------- | ||
| list[pl.Expr] | ||
| Two polars expressions: [sign_prefix, value_string] | ||
| """ | ||
| # Cast to Float64 first to handle columns that are entirely null (dtype `null`) | ||
| value = pl.col(col_name).cast(pl.Float64) | ||
| # Normalize -0.0 to +0.0: if abs(x) == 0 then 0.0 else x | ||
| normalized = pl.when(value.abs() == 0).then(pl.lit(0.0)).otherwise(value) | ||
| sign_prefix = pl.when(normalized >= 0).then(pl.lit("+")).otherwise(pl.lit("")) | ||
| return [sign_prefix, normalized.cast(pl.String)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about making it a simple case statement?
| def signed_number_expr(col_name: str) -> list[pl.Expr]: | |
| """ | |
| Return polars expressions for a signed number string, handling -0.0 correctly. | |
| This function returns two expressions: a sign prefix ('+' or '') and the | |
| number cast to string. It normalizes -0.0 to +0.0 to avoid producing invalid | |
| LP file syntax like "+-0.0" (which occurs because -0.0 >= 0 is True but | |
| str(-0.0) is "-0.0"). | |
| Parameters | |
| ---------- | |
| col_name : str | |
| Name of the column containing the numeric value. | |
| Returns | |
| ------- | |
| list[pl.Expr] | |
| Two polars expressions: [sign_prefix, value_string] | |
| """ | |
| # Cast to Float64 first to handle columns that are entirely null (dtype `null`) | |
| value = pl.col(col_name).cast(pl.Float64) | |
| # Normalize -0.0 to +0.0: if abs(x) == 0 then 0.0 else x | |
| normalized = pl.when(value.abs() == 0).then(pl.lit(0.0)).otherwise(value) | |
| sign_prefix = pl.when(normalized >= 0).then(pl.lit("+")).otherwise(pl.lit("")) | |
| return [sign_prefix, normalized.cast(pl.String)] | |
| def signed_number(expr: pl.Expr) -> pl.Expr: | |
| """ | |
| Return polar expressions for a signed number string, handling -0.0 correctly. | |
| Parameters | |
| ---------- | |
| expr : pl.Expr | |
| Numeric value | |
| Returns | |
| ------- | |
| pl.Expr | |
| value_string with sign | |
| """ | |
| return ( | |
| pl.when(expr > 0) | |
| .then(pl.format("+{}", expr) | |
| .when(expr < 0) | |
| .then(pl.format("{}", expr)) | |
| .when(expr.is_not_null()) | |
| .then(pl.lit("+0.0")) | |
| .otherwise(None) | |
| ) |
| cols = [ | ||
| pl.when(pl.col("coeffs") >= 0).then(pl.lit("+")).otherwise(pl.lit("")), | ||
| pl.col("coeffs").cast(pl.String), | ||
| *signed_number_expr("coeffs"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| *signed_number_expr("coeffs"), | |
| signed_number(pl.col("coeffs")), |
and equivalent down below.
Closes #541
Changes proposed in this Pull Request
Bug: LP file writer produced invalid syntax +-0.0 when bounds/coefficients were -0.0, causing Gurobi to reject the file.
Root cause: -0.0 >= 0 is True, so a + prefix was added, but str(-0.0) is "-0.0", producing +-0.0.
Fix: Normalize -0.0 to +0.0 using when(abs(x) == 0).then(0.0).otherwise(x) before formatting.
Performance: ~4% overhead on 10M rows
Changes:
Benchmark script
Checklist
doc.doc/release_notes.rstof the upcoming release is included.