Skip to main content

ENOMOTO-Solver

日本語版 README

ENOMOTO-Solver is a solver for linear optimization problems (LP) and mixed-integer linear optimization problems (MIP), consisting of a Python modelling interface and a Rust core.

Supported platforms: CPython 3.9 or later (tested on 3.9, 3.13 and 3.14) on the following operating systems. The wheels use abi3 (the stable ABI of Python), so a single wheel per OS installs on every supported version.

OS Architecture
Windows x86_64
Linux (glibc 2.28 or later, manylinux_2_28) x86_64, aarch64
macOS x86_64 (Intel), arm64 (Apple silicon)

For LP, it implements the slope–intercept dual two-phase method. This is a method that treats the M of the artificial-bound method (the big-M method applied to the dual problem), one of the phase-1 methods of the dual simplex method, symbolically instead of fixing it as a number.

  • Phase A (slope problem): starting from the all-slack basis, which is dual feasible without any computation, solve the problem obtained by replacing infinite bounds with ±1 and the right-hand side with 0. Its optimal value tells whether the original problem can have a finite optimal value.
  • Phase B (intercept problem): from the final basis of phase A, solve the problem obtained by fixing the slope with the ordinary dual simplex method.
  • Post-processing: if variables remain on artificial bounds, move them to finite bounds with a primal ratio test while keeping optimality.

In this way, without choosing a numerical M and with the dual simplex method alone, it decides whether an LP has a finite optimal value, is unbounded, or is infeasible, and returns an optimal basic solution when it has a finite optimal value.

The following are also implemented.

  • Proximal interior-point method + crossover: a proximal interior-point method (IP-PMM, Mehrotra's predictor–corrector method) computes an interior optimal solution, and a crossover (primal and dual push phases and basis selection) turns it into an optimal basic solution. The finish is done by the simplex method.
  • Parallel execution of the two methods: with threads set to 2 or more (the default is 1), an LP (for integer programs, a relaxation that cannot be warm-started) is solved at the same time by the slope–intercept dual two-phase method (1 thread) and the proximal interior-point method + crossover (the remaining threads), and the side that reaches a conclusion first is adopted (the other is stopped).
  • Mixed-integer linear optimization (MIP): solved by branch and cut (constraint propagation, cutting planes, primal heuristics, symmetry handling, etc.). All relaxations are solved by the slope–intercept dual two-phase method (warm-started from the parent's optimal basis at nodes), and when the root relaxation has no finite optimal value, whether the problem is infeasible or unbounded is decided with the problem in which the variables of the intercept problem are restricted to integers (when distinguish_infeasible_unbounded=True). Giving the solver several threads (threads set to 2 or more) parallelizes branch and bound: after the root is processed, the threads share the unprocessed nodes and divide the tree search among themselves (the root relaxation is solved at the same time by the two-phase method and the proximal interior-point method + crossover).

Installation

Install from PyPI (on the supported platforms above, a prebuilt wheel is installed, so Rust and other build tools are not needed).

pip install enomoto-solver

Building from source

For development or for running the benchmarks, clone the repository and build it. The following are needed.

  • Python 3.9 or later
  • A Rust toolchain (rustc, cargo)
  • maturin 1.5 or later (used to build the extension module)
  • Only for running the benchmarks: highspy and a C compiler (used to decompress the Netlib data)
git clone https://github.com/enomotokan/enomoto_solver.git
cd enomoto_solver
python -m venv .venv
# Windows: .venv\Scripts\activate   /   Linux, macOS: source .venv/bin/activate
pip install maturin
maturin develop --release

Always build with --release. A debug build is many times slower.

Usage

Defining and solving a problem

import math
from enomoto_solver import Model, Variable

M = Model()
x = Variable(float, 0, 10)          # continuous variable 0 <= x <= 10
y = Variable(float, 0, math.inf)    # bounds may be infinite
M.set_objective(x + 2 * y, sense="maximize")
M.add_constraint(x + y <= 10)
M.add_constraint(x - y >= -4)

sol = M.solve()
print(sol.status, sol.objective)    # optimal 17.0
print(x.value, y.value)             # 3.0 7.0
  • Variable(type, lower bound, upper bound) creates a variable in the most recently created Model (it can also be given explicitly with model=...). The type is float (continuous) or int (integer). Bounds may be ±math.inf (integer variables included). A free variable is Variable(float, -math.inf, math.inf).

  • Linear expressions are written with +, -, scalar multiplication and division by numbers, and constraints with <=, >= and ==. The objective and constraints are limited to linear expressions. Building an expression of degree two or higher, such as a product of variables (x * y), a power (x ** 2) or division by an expression (1 / x), raises a TypeError stating that it is not linear.

  • set_objective(f, sense="minimize" | "maximize") sets the objective function (the default is minimization).

  • Arguments of solve() (all optional):

    Argument Meaning
    threads Number of threads to use (default 1). With 2 or more, an LP is solved at the same time by the two-phase method and the interior-point method + crossover, and for integer programs the tree search is also shared. For details, see "Number of threads (threads)" below.
    root_solver The LP method (default "auto"; see "Choosing the LP method" below).
    distinguish_infeasible_unbounded Whether to distinguish infeasible from unbounded when there is no finite optimal value (default False; see "Result status, and infeasible or unbounded problems" below).
    time_limit, mip_rel_gap, node_limit Termination criteria for integer programs (seconds, relative gap (default 0), number of nodes).
    log Whether to output the log (default True; see "Log" below).
  • solve() always returns a Solution and raises no exception even when the problem is infeasible, unbounded or not solved. Judge the result by Solution.status. Only when status is "optimal" (for integer programs stopped at a limit, when there is an incumbent) does objective hold a value and can each variable's value be read with .value. Otherwise objective is None and reading .value raises RuntimeError.

Result status, and infeasible or unbounded problems

solve() returns a Solution that has the following attributes. status is one of "optimal", "infeasible", "unbounded", "infeasible_or_unbounded" and "not_solved" (for models with integer variables, there are also "time_limit" and "node_limit").

Attribute Meaning
status The result status (one of the above).
objective The objective value of the solution (in the original sense, including the constant term). It holds a value when the status is "optimal", and for integer programs stopped at a limit when there is an incumbent. Otherwise None.
node_limit_hit Whether an integer program was stopped by the limit on the number of nodes (node_limit). Always False for LP.
best_bound For integer programs, the proven bound on the optimal value (the lower bound for minimization, the upper bound for maximization). The optimal value lies between this value and objective. inf (-inf for maximization) if infeasible. None for LP.
mip_gap For integer programs, the relative gap |objective − best_bound| / max(|objective|, 1). The solve ends with "optimal" when it becomes mip_rel_gap or less. inf if there is no incumbent. None for LP.
nodes For integer programs, the number of branch-and-bound nodes processed (the total over all threads). None for LP.

By default, the solver stops when it finds that there is no finite optimal value (at the end of phase A) and returns "infeasible_or_unbounded". Passing distinguish_infeasible_unbounded=True makes it proceed to phase B and distinguish the two.

Linear optimization (LP)

M = Model()
z = Variable(float, -math.inf, math.inf)
M.set_objective(z)                  # minimize z
M.add_constraint(z <= 5)

sol = M.solve(distinguish_infeasible_unbounded=True)
print(sol.status)                   # unbounded

The same applies to models with integer variables. When phase A shows that the root relaxation has no finite optimal value, "infeasible_or_unbounded" is returned by default. With distinguish_infeasible_unbounded=True, the problem in which the variables of the intercept problem (the phase B problem) are restricted to integers is solved by branch and bound, and "unbounded" is returned if it has a feasible solution, "infeasible" if it is infeasible (because from a feasible solution x⁰ of the intercept problem and the slope x¹ of phase A, x⁰ + M x¹ becomes an integer solution that lowers the objective value without bound).

Choosing the LP method

solve(root_solver=...) selects the LP method (it applies only to models without integer variables; the relaxations of integer programs are always solved by the slope–intercept dual two-phase method with warm starts; specifying anything other than "auto" for a model with integer variables issues a UserWarning and the setting is ignored).

root_solver Method
"auto" (default; None is the same) If threads is 2 or more, run the slope–intercept dual two-phase method (1 thread) and the proximal interior-point method + crossover (the remaining threads) in parallel and adopt the side that reaches a conclusion first. With threads=1 (the default), solve with the two-phase method only.
"simplex" The slope–intercept dual two-phase method only.
"ipm_crossover" The proximal interior-point method + crossover only. If the interior-point method does not converge, the problem is solved again by the two-phase method.
"interior" A proximal interior-point method (IP-PMM) with its own independent presolve. It does not give a basic solution and is intended for verification.

Whatever the method, infeasibility and unboundedness are decided by the two-phase method. With "auto", the interior-point method + crossover side is adopted only when it yields a basic solution whose optimality has been confirmed by the simplex method.

Mixed-integer linear optimization (MIP)

M = Model()
items = [(Variable(int, 0, 1), value, weight) for value, weight in [(60, 10), (100, 20), (120, 30)]]
M.set_objective(sum(v * value for v, value, _ in items), sense="maximize")
M.add_constraint(sum(v * weight for v, _, weight in items) <= 50)
sol = M.solve(time_limit=60)
print(sol.objective, [round(v.value) for v, _, _ in items])   # 220.0 [0, 1, 1]
print(sol.best_bound, sol.mip_gap, sol.nodes)                 # 220.0 0.0 0

Models with integer variables are solved by branch and cut, which solves the relaxations with the slope–intercept dual two-phase method.

  • Presolve and constraint propagation: merging of parallel rows, probing, activity-based bound tightening, conflict analysis and dual proofs.
  • Cutting planes: CMIR (including tableau rows equivalent to Gomory cuts), lifted flow cover, {0, 1/2}-Chvátal–Gomory (zerohalf).
  • Primal heuristics: rounding, fix-and-propagate, Feasibility Pump, Feasibility Jump, diving, rounding from the interior-point solution, improvement by sub-MIPs (RINS, RENS, DINS, Crossover, etc.).
  • Search: reliability branching by pseudocosts, plunging (diving into child nodes with warm starts), restarts, symmetry detection and symmetry-breaking inequalities (including orbitopes).

When threads is k ≥ 2, the search runs in parallel as follows.

  • Relaxations that cannot be warm-started: the root LP and the root LP after a restart have no basis to start from, so they are solved at the same time by the two-phase method (1 thread) and the proximal interior-point method + crossover (k − 1 threads). Node relaxations are solved by the two-phase method warm-started from the parent's optimal basis (they finish in a few iterations, so running the interior-point method at the same time has no chance of winning and only adds work). Sub-MIPs solved inside heuristics (RINS, etc.) are often small, so even their solves without a basis use the two-phase method only. After the tree search starts using k threads, no parallel execution is done.
  • Root: the root cutting planes and heuristics are performed by the thread that solved the root (the main thread).
  • Tree search: when the main thread has two unprocessed nodes, the remaining k − 1 threads are started from a copy of the tree after the root processing (for problems solved at the root, no additional threads are created). Each thread processes the nodes in its own queue. When a thread's queue becomes empty, the other threads hand it nodes with small lower bounds through a shared queue. The search ends when all threads are waiting and the shared queue is also empty.
  • Information shared between threads: incumbents (the best objective value is used for pruning in all threads), global bound tightenings, pseudocost observations (merged every 16 nodes), and dual proofs and conflicts (imported every 64 nodes).
  • Since the order in which nodes are processed changes with the progress of the threads, with k ≥ 2 the number of nodes and the solve time may vary from run to run (the optimal value obtained does not change).

Termination criteria are specified with time_limit (seconds), mip_rel_gap (relative gap, default 0) and node_limit (number of nodes). When the solver stops at a limit, status becomes "time_limit" or "node_limit", and if there is an incumbent, objective and each variable's .value can be read. The results are also stored in best_bound (the proven bound), mip_gap (relative gap) and nodes (number of nodes processed) of Solution.

The relative gap measures the difference between the objective value of the incumbent (the feasible solution with the best objective value found during the search) and the proven bound as

relative gap = |objective value of the incumbent − proven bound| / max(|objective value of the incumbent|, 1)

(the denominator is set to 1 when it is less than 1, so that the ratio does not become extremely large when the objective value is close to 0). The optimal value lies between the proven bound and the objective value of the incumbent. During the search, nodes whose bound reaches "objective value of the incumbent − max(mip_rel_gap × max(|objective value of the incumbent|, 1), 10⁻⁹ × max(|objective value of the incumbent|, 1))" are pruned (when every objective coefficient of the integer variables is a multiple of a common step and there are no continuous variables in the objective, nodes that cannot improve by one step are pruned instead), so when no unprocessed nodes remain, the relative gap is mip_rel_gap or less (with the default mip_rel_gap=0, 10⁻⁹ or less). The solve then ends with "optimal". With the default, "optimal" therefore means that the solution has been proven optimal up to the tolerances of the LP relaxations; specify e.g. mip_rel_gap=1e-4 to stop earlier with a looser guarantee.

The solution returned is polished before it is reported: integer variables are rounded to exact integers, and if there are continuous variables, the LP of the original problem with the integer variables fixed to those values is solved again and its solution is taken when it violates the constraints no more than the original one and its objective value is not worse. objective is then recomputed from the returned values with compensated summation, so it agrees with the objective of the returned solution up to rounding error (on the MIPLIB 2017 instances in benchmarks/, the relative error against the reference optimal values is 10⁻¹⁵ or less).

Number of threads (threads)

solve(threads=...) specifies the number of threads to use (the default is 1). A value above the number of CPUs available to this process (enomoto_solver.max_threads()) is capped there. The whole solve, including parallel sections such as presolve, runs on this number of threads.

threads LP (root_solver="auto") MIP
1 (default) Solved by the slope–intercept dual two-phase method only. Both the relaxations and the tree search are solved on 1 thread.
k ≥ 2 Solved at the same time by the two-phase method (1 thread) and the proximal interior-point method + crossover (k − 1 threads), adopting the side that reaches a conclusion first. Relaxations that cannot be warm-started (the root, etc.) are solved at the same time in the same way as on the left, and after the root is processed the tree is shared among k threads for the search ("Parallel search" above).

In parallel execution, the threads are divided before starting into separate pools for the two-phase method (1 thread) and for the interior-point method + crossover (the rest), so the two methods never take each other's threads. If the two-phase method finishes first, its basic solution is used as it is; if the interior-point method + crossover finishes first, the optimal basis obtained in the crossover's finish (the main loop of the two-phase method) is used as it is. The losing side is stopped, and its termination is not waited for.

import enomoto_solver
sol = M.solve(threads=enomoto_solver.max_threads())   # use all available CPUs

Log

By default, solve() outputs to standard output first a notice of the author, the license and the contact, together with the size of the problem, then the key points during the solve, and finally the result. The key points during the solve are as follows.

  • LP: the size after presolve, the method used, and in parallel execution which method reached a conclusion first.
  • MIP: the size after presolve, the value of the root LP, the change of the value by the root cutting planes, restarts, and the progress of the search (elapsed time, number of nodes processed, number of unprocessed nodes, proven bound, objective value of the incumbent and relative gap; every 5 seconds).

For the example in "Mixed-integer linear optimization (MIP)" above, the output is as follows.

ENOMOTO-Solver 0.1.0 (MIT License)
Author: Kan Enomoto (https://enomoto-math-labo.com/en)
For optimization consulting requests and bug reports, contact enomotokan@gmail.com

Problem: 3 variables (3 integer), 1 constraints, 3 nonzeros, maximize; threads 1
Presolve: 1 rows, 3 columns -> 1 rows, 3 columns (0.00s)
Root LP: objective 2.2000000000e2 (0.00s)
     time      nodes      open        best bound         incumbent       gap
     0.0s          0         0     2.200000000e2     2.200000000e2     0.00%
Result: optimal, objective 220, best bound 220, gap 0.00%, nodes 0 (0.00s)

The columns of the search progress mean the following.

Column Meaning
time Elapsed time since the start of the solve.
nodes Number of nodes processed (whose relaxations have been solved), the total over all threads.
open Number of unprocessed nodes: subproblems created by branching that are still in the queue and whose relaxations have not been solved yet (the total over all threads). The search ends when it becomes 0.
best bound The proven bound (the lower bound on the optimal value for minimization, the upper bound for maximization).
incumbent The objective value of the incumbent (the feasible solution with the best objective value found). - if there is none yet.
gap The relative gap ("Mixed-integer linear optimization (MIP)" above) as a percentage.

With solve(log=False), neither the notice nor the log is output.

Benchmark results

We compared with HiGHS 1.15.1, CLP 1.17.11 and SoPlex 8.1.0 on 207 LPs (measured on 29–30 September 2026). Each entry is "number of problems solved / shifted geometric mean of the solve time (seconds)" (shift 10 s; problems not solved are counted at the time limit of 600 s). Bold marks the best value in each row.

Problem set ENOMOTO HiGHS CLP SoPlex
Netlib, with a finite optimal solution (93 problems) 93 / 0.152 93 / 0.166 93 / 0.164 92 / 0.741
Kennington (16 problems) 16 / 0.964 16 / 1.68 16 / 1.16 16 / 5.76
Mittelmann LPopt (40 problems) 12 / 377 11 / 405 18 / 393 7 / 503
Netlib infeasible (29 problems) 29 / 0.00597 29 / 0.0154 29 / 0.0743 28 / 1.53
Duals of the infeasible problems, unbounded (29 problems) 29 / 0.0588 29 / 0.335 28 / 2.01 29 / 0.0332
All (207 problems) 179 / 10.6 178 / 11.0 184 / 11.3 172 / 13.4

The geometric mean over all 207 problems is 0.084 s for ENOMOTO, 0.183 s for HiGHS, 0.100 s for CLP and 0.146 s for SoPlex. ENOMOTO did not return a single wrong answer. On the 27 unbounded problems for which phase A detected that there is no finite optimal solution, the additional time needed to distinguish unbounded from infeasible was about 2% of the solve time.

  • Machine: a laptop with an AMD Ryzen 7 5700U (8 cores 16 threads, 16 GB memory), Ubuntu 22.04 on WSL2 (memory limit 12 GB). ENOMOTO and HiGHS use 16 threads; CLP and SoPlex are sequential. All settings other than the time limit and the number of threads are the defaults.
  • Times cover the solve call only (reading the MPS file and building the model are not included; presolve and postsolve are included) and are the median of 3 runs (1 run if it took 300 s or more). Each run is solved in a new process.
  • Mittelmann consists of the 40 problems obtained by removing the four largest problems that do not fit in this machine's memory (thk_48, L2CTA3D, dlr2, Dual2_5000) from the 44 public problems available from plato.asu.edu.
  • The full tables, per-problem times and raw data are in benchmarks/paper/summary.md, per_problem.csv and results.json. For the reproduction procedure (Linux), see scripts/paper_bench/README.md.

Mixed-integer linear optimization (MIPLIB 2017)

We compared with HiGHS 1.15.1 on 40 small to medium problems from MIPLIB 2017 (measured on 12 October 2026). Both solvers were given 16 threads and a time limit of 60 s.

ENOMOTO HiGHS
Problems solved / shifted geometric mean of the solve time (s) 22 / 22.3 20 / 25.4

On the problems that both solvers proved optimal, no objective values disagreed. The per-problem results are below. "t" means the run was stopped by the time limit, and the objective is then the value of the best integer solution (incumbent) at that point ("-" means no integer solution). Bold marks the faster time, or, on problems where both solvers were stopped, the better objective.

Problem ENOMOTO [s] HiGHS [s] ENOMOTO objective HiGHS objective
gen-ip002 t t -4783.73 -4762.79
gen-ip054 t t 6852.68 6858.88
pk1 t t 11 14
gr4x6 0.01 0.07 202.35 202.35
markshare_4_0 3.46 t 1 1
mas74 t t 11801.2 12014.9
mas76 39.42 t 40005.1 40005.1
neos5 t t 15 15
p0201 2.28 0.92 7615 7615
misc07 8.25 32.60 2810 2810
flugpl 0.03 0.11 1.2015e+06 1.2015e+06
dcmulti 1.10 1.37 188182 188182
gt2 0.08 0.06 21166 21166
khb05250 0.79 0.34 1.0694e+08 1.0694e+08
qnet1 3.65 0.76 16029.7 16029.7
rout 9.76 38.98 1077.56 1077.56
10teams 5.99 1.91 924 924
air03 0.78 3.37 340160 340160
blend2 0.56 3.37 7.59898 7.59898
fiber 1.70 1.09 405935 405935
noswot t t -41 -41
qiu 36.18 t -132.873 -132.873
neos-911970 t 13.27 58.34 54.76
30n20b8 t t - 553
binkar10_1 33.68 27.44 6742.2 6742.2
neos-860300 34.44 20.09 3201 3201
eil33-2 42.06 t 934.008 987.674
nw04 8.11 t 16862 52674
assign1-5-8 t t 214 212
beavma 1.00 0.29 383285 383285
blp-ar98 t t 6995.88 -
ran14x18-disj-8 t t 3744 3744
h80x6320d t 21.24 6757.85 6382.1
supportcase26 t t 1946.56 1772.96
timtab1 t 55.36 880252 764774
bppc4-08 t t 62 53
cvs16r128-89 t t -35 -3
mik-250-20-75-4 5.00 18.54 -52301 -52301
neos-1456979 t t - 205
graph20-20-1rand t t -6 -8
  • Machine: a laptop with an AMD Ryzen 7 5700U (8 cores 16 threads, 16 GB memory), Windows 11. All settings other than the time limit and the number of threads are the defaults (the default relative gap is 0 for ENOMOTO and 1e-4 for HiGHS).
  • Times cover the solve call only (reading the MPS file and building the model are not included) and come from a single run. Each problem is solved in a separate subprocess; the shifted geometric mean uses a shift of 10 s and counts unsolved problems at 60 s.
  • The raw data are in benchmarks/miplib_results_16t_60s_20261012.json. Reproduce with python scripts/run_miplib_benchmark.py --time-limit 60 --threads 16 (the problems are downloaded from the MIPLIB site on the first run).

Easy comparison with HiGHS on Netlib

The Netlib LP data has no license that explicitly permits redistribution, so it is not included in this repository (docs/netlib-data.md). Download and decompress it only once at the start.

python scripts/setup_netlib_data.py          # requires a network connection and a C compiler

Then compare with HiGHS (pip install highspy).

python -m enomoto_solver.benchmark_highs --max-vars 100000 --out netlib_results.csv

Each problem is solved in a separate subprocess. The measured time covers the solve call only (reading the MPS file and building the model are not included), and for both solvers it includes presolve and postsolve.

Directory structure

Path Contents
python/enomoto_solver/ Python modelling interface (Model, Variable, Function, Constraint) and benchmark scripts
src/model.rs Entry point called from Python
src/presolve.rs, src/presolve/ Presolve (scaling, row and column reductions, postsolve)
src/simplex.rs Construction of the standard form, decomposition into connected components, primal simplex method
src/simplex/slope_intercept_dual.rs Slope–intercept dual two-phase method
src/simplex/lu.rs Sparse LU factorization and Forrest–Tomlin update
src/simplex/crossover.rs Proximal interior-point method + crossover
src/simplex/race.rs Parallel execution of the two-phase method and the interior-point method + crossover
src/interior_point.rs, src/interior_point/ Proximal interior-point method (IP-PMM) and factorization of the KKT system
src/solver.rs Dispatch of the LP method (root_solver)
src/mip/ Branch and cut (constraint propagation, cutting planes, heuristics, symmetry)
src/params.rs Tolerances and tuning parameters
docs/improvement_history.md Record of implementation changes and their measured results
benchmarks/netlib_dev_results.csv Netlib measurements taken during development (one row is one problem and one measurement; the source_file column is the name of the original result file)
benchmarks/paper/ Comparison with HiGHS, CLP and SoPlex included in the paper (207 problems, 600 s limit; scripts/paper_bench/)
benchmarks/mittelmann_results.* Comparison with HiGHS on an earlier Mittelmann LPopt benchmark (2026-09-24, 44 public problems, 600 s limit; scripts/run_mittelmann_benchmark.py, Linux only)

Paper

The method and the numerical experiments are summarized in the following paper.

Kan Enomoto, "Complete classification of linear programming problems by the dual simplex method alone: the slope–intercept dual two-phase method" (in preparation).

Use of generative AI

The idea and theory of the slope–intercept dual two-phase method for linear optimization and mixed-integer optimization are the author's. Most of the implementation was done by an AI coding assistant (Claude by Anthropic) under the author's design and direction, and the author decided which implementation techniques to adopt based on comparative experiments.

License

MIT License © 2026 Kan Enomoto

Metadata

Release files for enomoto-solver 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for enomoto-solver 0.1.2
File Size Uploaded
enomoto_solver-0.1.2.tar.gz 937.0 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for enomoto-solver 0.1.2
File
enomoto_solver-0.1.2-cp39-abi3-win_amd64.whl CPython 3.9 abi3 Windows x86-64 Details
enomoto_solver-0.1.2-cp39-abi3-manylinux_2_28_x86_64.whl CPython 3.9 abi3 Linux glibc 2.28+ x86-64 Details
enomoto_solver-0.1.2-cp39-abi3-manylinux_2_28_aarch64.whl CPython 3.9 abi3 Linux glibc 2.28+ ARM64 Details
enomoto_solver-0.1.2-cp39-abi3-macosx_11_0_arm64.whl CPython 3.9 abi3 macOS 11.0+ ARM64 Details
enomoto_solver-0.1.2-cp39-abi3-macosx_10_12_x86_64.whl CPython 3.9 abi3 macOS 10.12+ x86-64 Details

Total release size: 18.4 MB

Release files / enomoto_solver-0.1.2.tar.gz

Download URL enomoto_solver-0.1.2.tar.gz
Size 937.0 kB
Tags Source
SHA-256 checksum
How to use checksums
8db4aefd2a748983a6ddab18264a0f86db4e0c9d0e22ae0392f7baa42bb04b8a
BLAKE2b-256 checksum
How to use checksums
a61c8c23dda4a1235e1f954b775f7c2e9fdc7f4d794a970cd2a16426115cb30f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release files / enomoto_solver-0.1.2-cp39-abi3-win_amd64.whl

Download URL enomoto_solver-0.1.2-cp39-abi3-win_amd64.whl
Size 3.4 MB
Tags CPython 3.9 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
337a22e04d2951bb2913b80335455a0df9fd2a0a1f74eb8c0847e32b8a6bb38b
BLAKE2b-256 checksum
How to use checksums
ee353f37491d45cdf2d44cf6aebdafabb214a132fdfd53db845ef8f670b0d87e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release files / enomoto_solver-0.1.2-cp39-abi3-manylinux_2_28_x86_64.whl

Download URL enomoto_solver-0.1.2-cp39-abi3-manylinux_2_28_x86_64.whl
Size 3.9 MB
Tags CPython 3.9 Linux glibc 2.28+ x86-64 abi3
SHA-256 checksum
How to use checksums
f10ddbe8a3bcf43e865f88413d80340d698c298fe5ae628d6bc65db873c7c56d
BLAKE2b-256 checksum
How to use checksums
1ede8a16af35a7ba3787fd0471a081bf3d7d49daab348e9b42ca9c4da64d207e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release files / enomoto_solver-0.1.2-cp39-abi3-manylinux_2_28_aarch64.whl

Download URL enomoto_solver-0.1.2-cp39-abi3-manylinux_2_28_aarch64.whl
Size 3.5 MB
Tags CPython 3.9 Linux glibc 2.28+ ARM64 abi3
SHA-256 checksum
How to use checksums
7c472686500539cdcaf237c162e69bb2571fa777b771e2b7c5836c18ee8bc9ef
BLAKE2b-256 checksum
How to use checksums
498ba0cf4397d53cdbf1ce07d4f927dcbf8453e349280a7162604068361d5c8a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release files / enomoto_solver-0.1.2-cp39-abi3-macosx_11_0_arm64.whl

Download URL enomoto_solver-0.1.2-cp39-abi3-macosx_11_0_arm64.whl
Size 3.1 MB
Tags CPython 3.9 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
2c62dbbc45d2918181fef47cbef8c3e0286166f28c828fc897a01ea6ffa88f3f
BLAKE2b-256 checksum
How to use checksums
26c402a20742eae4c3387cfe2a10ee52bc5ddb08b3182a3565cb6b0c5255d117
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release files / enomoto_solver-0.1.2-cp39-abi3-macosx_10_12_x86_64.whl

Download URL enomoto_solver-0.1.2-cp39-abi3-macosx_10_12_x86_64.whl
Size 3.6 MB
Tags CPython 3.9 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
9e28bf90e68775caa3879feefd9b62a4bb73f18174db8310f6b2c78b76cd2b67
BLAKE2b-256 checksum
How to use checksums
a62cd50c795d17e45c667d603d991255213cdd9727dc3f50cb46833620998db0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.2 This release

6 release files

0.1.1

6 release files

0.1.0

6 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page