Skip to content
Open
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
3 changes: 2 additions & 1 deletion process/core/final.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from process.core.solver import constraints
from process.core.solver.objectives import objective_function
from process.data_structure import numerics
from process.data_structure.numerics import SolverOutputCondition


def finalise(models, data, ifail: int, non_idempotent_msg: str | None = None):
Expand All @@ -26,7 +27,7 @@ def finalise(models, data, ifail: int, non_idempotent_msg: str | None = None):
non_idempotent_msg : None | str, optional
warning about non-idempotent variables, defaults to None
"""
if ifail == 1:
if ifail == SolverOutputCondition.CONVERGED:
po.oheadr(constants.NOUT, "Final Feasible Point")
else:
po.oheadr(constants.NOUT, "Final UNFEASIBLE Point")
Expand Down
5 changes: 3 additions & 2 deletions process/core/io/vary_run/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
process_warnings,
set_variable_in_indat,
)
from process.data_structure.numerics import SolverOutputCondition

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -156,7 +157,7 @@ def __next__(self):
m_file = MFile(filename=self.wdir / mfile)
ifail = m_file.data["ifail"].get_scan(-1)

if ifail != 1:
if ifail != SolverOutputCondition.CONVERGED:
print(f"VaryRun iteration {self._current_iteration} did not converge.\n")
else:
print(
Expand Down Expand Up @@ -243,7 +244,7 @@ def error_status2readme(self, mfile):
error_status = "The MFILE is empty. PROCESS probably exited prematurely.\n"

ifail = m_file.data["ifail"].get_scan(-1)
if ifail != 1:
if ifail != SolverOutputCondition.CONVERGED:
ifail_msg = f"PROCESS has been unable to find a converging input file within the chosen maximum number of iterations.\nYou could try increasing the maximum number of iterations (which is currently set to {self.niter}),\nchanging the factor within which the iteration variables are changed,\nor by changing the initial values of the iteration variables."
else:
ifail_msg = f"PROCESS found a converged solution using VaryRun. The converging input file is {self._current_iteration - 1}_IN.DAT"
Expand Down
7 changes: 4 additions & 3 deletions process/core/io/vary_run/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from process.core.io.in_dat import InDat
from process.core.io.mfile import MFile
from process.data_structure import numerics
from process.data_structure.numerics import SolverOutputCondition

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -250,13 +251,13 @@ def no_unfeasible_mfile(wdir=".", mfile="MFILE.DAT"):

# no scans
if not m_file.data["isweep"].exists:
if m_file.get("ifail") == 1:
if m_file.get("ifail") == SolverOutputCondition.CONVERGED:
return 0
return 1

ifail = m_file.data["ifail"].get_scans()
try:
return len(ifail) - ifail.count(1)
return len(ifail) - ifail.count(SolverOutputCondition.CONVERGED)
except TypeError:
# This seems to occur, if ifail is not in MFILE!
# This probably means in the mfile library a KeyError
Expand Down Expand Up @@ -317,7 +318,7 @@ def get_solution_from_mfile(neqns, nvars, wdir=".", mfile="MFILE.DAT"):
table_sol = [m_file.get(f"itvar{var_no + 1:03}") for var_no in range(nvars)]
table_res = [m_file.get(f"normres{con_no + 1:03}") for con_no in range(neqns)]

if ifail != 1:
if ifail != SolverOutputCondition.CONVERGED:
return ifail, "0", "0", ["0"] * nvars, ["0"] * neqns

return ifail, objective_function, constraints, table_sol, table_res
Expand Down
21 changes: 11 additions & 10 deletions process/core/scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
scan_variables,
tfcoil_variables,
)
from process.data_structure.numerics import SolverOutputCondition

if TYPE_CHECKING:
from process.core.model import DataStructure, Model
Expand Down Expand Up @@ -272,7 +273,7 @@ def post_optimise(self, ifail: int):
process_output.ocmmnt(
constants.NOUT, "PROCESS has performed a VMCON (optimisation) run."
)
if ifail != 1:
if ifail != SolverOutputCondition.CONVERGED:
process_output.ovarin(constants.NOUT, "Error flag", "(ifail)", ifail)
process_output.oheadr(
constants.IOTTY, "PROCESS COULD NOT FIND A FEASIBLE SOLUTION"
Expand Down Expand Up @@ -397,7 +398,7 @@ def post_optimise(self, ifail: int):
process_output.oblnkl(constants.NOUT)

if self.solver == "fsolve":
if ifail == 1:
if ifail == SolverOutputCondition.CONVERGED:
msg = "PROCESS has solved using fsolve."
else:
msg = "PROCESS failed to solve using fsolve."
Expand All @@ -406,7 +407,7 @@ def post_optimise(self, ifail: int):
f"{msg}\n",
)
else:
if ifail == 1:
if ifail == SolverOutputCondition.CONVERGED:
string1 = "PROCESS has successfully optimised"
else:
string1 = "PROCESS has failed to optimise"
Expand Down Expand Up @@ -675,10 +676,10 @@ def verror(self, ifail: int):
ifail: int :

"""
if ifail == -1:
if ifail == SolverOutputCondition.USER_TERMINATED:
process_output.ocmmnt(constants.NOUT, "User-terminated execution of VMCON.")
process_output.ocmmnt(constants.IOTTY, "User-terminated execution of VMCON.")
elif ifail == 0:
elif ifail == SolverOutputCondition.IMPROPER_INPUT:
process_output.ocmmnt(
constants.NOUT, "Improper input parameters to the VMCON routine."
)
Expand All @@ -688,7 +689,7 @@ def verror(self, ifail: int):
constants.IOTTY, "Improper input parameters to the VMCON routine."
)
process_output.ocmmnt(constants.IOTTY, "PROCESS coding must be checked.")
elif ifail == 2:
elif ifail == SolverOutputCondition.MAX_ITERATIONS:
process_output.ocmmnt(
constants.NOUT,
"The maximum number of calls has been reached without solution.",
Expand Down Expand Up @@ -729,7 +730,7 @@ def verror(self, ifail: int):
constants.IOTTY,
"Try changing the variables in IXC, or modify their initial values.",
)
elif ifail == 3:
elif ifail == SolverOutputCondition.MAX_LINE_SEARCHES:
process_output.ocmmnt(
constants.NOUT, "The line search required the maximum of 10 calls."
)
Expand All @@ -749,7 +750,7 @@ def verror(self, ifail: int):
process_output.ocmmnt(
constants.IOTTY, "Try changing or adding variables to IXC."
)
elif ifail == 4:
elif ifail == SolverOutputCondition.UPHILL_SEARCH:
process_output.ocmmnt(
constants.NOUT, "An uphill search direction was found."
)
Expand All @@ -765,7 +766,7 @@ def verror(self, ifail: int):
constants.IOTTY, "Try changing the equations in ICC, or"
)
process_output.ocmmnt(constants.IOTTY, "adding new variables to IXC.")
elif ifail == 5:
elif ifail == SolverOutputCondition.NO_SOLUTION:
process_output.ocmmnt(
constants.NOUT, "The quadratic programming technique was unable to"
)
Expand Down Expand Up @@ -793,7 +794,7 @@ def verror(self, ifail: int):
"their initial values (especially if only 1 optimisation",
)
process_output.ocmmnt(constants.IOTTY, "iteration was performed).")
elif ifail == 6:
elif ifail == SolverOutputCondition.SINGULAR_MATRIX_OR_BOUNDS:
process_output.ocmmnt(
constants.NOUT, "The quadratic programming technique was restricted"
)
Expand Down
7 changes: 4 additions & 3 deletions process/core/solver/solver_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
)
from process.core.solver.solver import get_solver
from process.data_structure import numerics
from process.data_structure.numerics import SolverOutputCondition


class SolverHandler:
Expand Down Expand Up @@ -60,7 +61,7 @@ def run(self):

# If VMCON optimisation has failed then try altering value of epsfcn
if self.solver_name == "vmcon":
if ifail != 1:
if ifail != SolverOutputCondition.CONVERGED:
print("Trying again with new epsfcn")
# epsfcn is only used in evaluators.Evaluators()
# TODO epsfcn could be set in Evaluators instance now, don't need to
Expand All @@ -73,7 +74,7 @@ def run(self):
# to next attempt
numerics.epsfcn /= 10 # reset value

if ifail != 1:
if ifail != SolverOutputCondition.CONVERGED:
print("Trying again with new epsfcn")
numerics.epsfcn /= 10 # try new smaller value
print("new epsfcn = ", numerics.epsfcn)
Expand All @@ -83,7 +84,7 @@ def run(self):
# If VMCON has exited with error code 5 try another run using a multiple
# of the identity matrix as input for the Hessian b(n,n)
# Only do this if VMCON has not iterated (nviter=1)
if ifail == 5 and numerics.nviter < 2:
if ifail == SolverOutputCondition.NO_SOLUTION and numerics.nviter < 2:
print(
"VMCON error code = 5. Rerunning VMCON with a new initial "
"estimate of the second derivative matrix."
Expand Down
32 changes: 32 additions & 0 deletions process/data_structure/numerics.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
from enum import IntEnum

import numpy as np


class SolverOutputCondition(IntEnum):
"""Enum for the possible conditions that can be returned by the solvers.
This is for the `ifail` condition
"""
Comment on lines +6 to +9

USER_TERMINATED = -1

IMPROPER_INPUT = 0
"""Solver failed due to improper input (e.g. invalid parameters, or failure to
satisfy solver preconditions)"""
CONVERGED = 1
"""Solver converged successfully"""

MAX_ITERATIONS = 2
"""Solver failed to converge within the maximum number of iterations"""

MAX_LINE_SEARCHES = 3
"""Line search required 10 function calls without finding a better solution"""

UPHILL_SEARCH = 4
"""Uphill search direction was calculated"""

NO_SOLUTION = 5
"""No feasible solution or bad approximation of Hessian"""

SINGLE_MATRIX_OR_BOUNDS = 6
"""Singular matrix in quadratic subproblem or restriction by artificial bounds"""
Comment on lines +13 to +32


ipnvars: int = 177
"""total number of variables available for iteration"""

Expand Down
5 changes: 3 additions & 2 deletions tests/integration/test_vmcon.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from process.core.init import init_all_module_vars
from process.core.solver.evaluators import Evaluators
from process.core.solver.solver import get_solver
from process.data_structure.numerics import SolverOutputCondition

# Debug-level terminal output logging
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -75,7 +76,7 @@ def __init__(self):
self.errlm = 0.0
self.errcom = 0.0
self.errcon = 0.0
self.ifail = 1
self.ifail = SolverOutputCondition.CONVERGED


class CustomFunctionEvaluator(ABC, Evaluators):
Expand Down Expand Up @@ -507,7 +508,7 @@ def get_case3():
case.exp.vlam = np.array([0.0, 0.0])
case.exp.errlg = 1.599997724349894
case.exp.errcon = 8.0000000000040417e-01
case.exp.ifail = 5
case.exp.ifail = SolverOutputCondition.NO_SOLUTION

return case

Expand Down
6 changes: 5 additions & 1 deletion tests/regression/test_process_input_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from regression_test_assets import RegressionTestAssetCollector

from process.core.io.mfile import MFile
from process.data_structure.numerics import SolverOutputCondition
from process.main import process_cli

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -116,7 +117,10 @@ def compare(

ifail = mfile.data["ifail"].get_scan(-1)

assert ifail == 1 or mfile.data["ioptimz"].get_scan(-1) == -2, (
assert (
ifail == SolverOutputCondition.CONVERGED
or mfile.data["ioptimz"].get_scan(-1) == -2
), (
f"\033[0;36m ifail of {ifail} indicates PROCESS did not solve successfully\033[0m"
)

Expand Down
7 changes: 6 additions & 1 deletion tracking/tracking_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
from bokeh.resources import CDN

from process.core.io import mfile as mf
from process.data_structure.numerics import SolverOutputCondition

logging.basicConfig(level=logging.INFO, filename="tracker.log")
logger = logging.getLogger("PROCESS Tracker")
Expand Down Expand Up @@ -182,7 +183,11 @@ def __init__(
"""
self.mfile = mf.MFile(mfile)

if strict and (ifail := self.mfile.data["ifail"].get_scan(-1)) != 1:
if (
strict
and (ifail := self.mfile.data["ifail"].get_scan(-1))
!= SolverOutputCondition.CONVERGED
):
raise RuntimeError(
f"{ifail = :.0f} indicates PROCESS has failed to converge."
)
Expand Down
Loading