Debugging the solution process

The debugging tutorial covers specification errors on a small model. This tutorial goes one step further: it walks through the solution process of a larger model and shows how to use the solver’s inspection tools when the solve fails.

  1. how the problem reduction, the block decomposition and the starting values of a successful solve can be inspected,

  2. how structural over- and underdetermination is reported and what a structurally singular but count-balanced problem looks like,

  3. how the specifications shape the block decomposition,

  4. how to run the solution process in stages with :code:presolve and :code:solve_continue and

  5. how to pause the solver at a failed block, inspect the failure and decide whether the starting values or the specification are the problem.

Background information on all steps of the solution process is available in the solver section. The outputs shown here are based on the following version of tespy:

from tespy import __version__
__version__
"0.11.0 - Rankine's Renaissance"

The example model

The model is an Organic Rankine Cycle with an internal recuperator, an air cooled condenser and a geothermal heat source, the same plant as in the workflow integration section.

../_images/flowsheet.svg

Flowsheet of the recuperated ORC system

../_images/flowsheet_darkmode.svg

Flowsheet of the recuperated ORC system

The working fluid is isopentane. The evaporation level is fixed through saturated turbine inlet at 150 °C, the condensation level through the air temperatures and the pinch specifications of the condenser.

Note

Since the following sections modify the model repeatedly, the setup lives in a function returning a freshly parametrized network.

from tespy.components import (
    CycleCloser, Generator, Motor, MovingBoundaryHeatExchanger,
    PowerBus, PowerSink, Pump, Sink, Source, Turbine,
)
from tespy.connections import Connection, PowerConnection
from tespy.networks import Network


def build_orc():
    nw = Network(iterinfo=False)
    nw.units.set_defaults(
        temperature="degC", pressure="bar", pressure_difference="bar",
        power="kW", heat="kW"
    )

    turbine = Turbine("turbine")
    recuperator = MovingBoundaryHeatExchanger("recuperator")
    condenser = MovingBoundaryHeatExchanger("condenser")
    pump = Pump("pump")
    preheater = MovingBoundaryHeatExchanger("preheater")
    evaporator = MovingBoundaryHeatExchanger("evaporator")
    cc = CycleCloser("cc")

    heat_source = Source("heat source")
    heat_outflow = Sink("heat outflow")
    air_source = Source("air source")
    air_sink = Sink("air sink")

    a1 = Connection(heat_source, "out1", evaporator, "in1", label="a1")
    a2 = Connection(evaporator, "out1", preheater, "in1", label="a2")
    a3 = Connection(preheater, "out1", heat_outflow, "in1", label="a3")

    b1 = Connection(cc, "out1", turbine, "in1", label="b1")
    b2 = Connection(turbine, "out1", recuperator, "in1", label="b2")
    b3 = Connection(recuperator, "out1", condenser, "in1", label="b3")
    b4 = Connection(condenser, "out1", pump, "in1", label="b4")
    b5 = Connection(pump, "out1", recuperator, "in2", label="b5")
    b6 = Connection(recuperator, "out2", preheater, "in2", label="b6")
    b7 = Connection(preheater, "out2", evaporator, "in2", label="b7")
    b8 = Connection(evaporator, "out2", cc, "in1", label="b8")

    c1 = Connection(air_source, "out1", condenser, "in2", label="c1")
    c2 = Connection(condenser, "out2", air_sink, "in1", label="c2")

    nw.add_conns(a1, a2, a3, b1, b2, b3, b4, b5, b6, b7, b8, c1, c2)

    generator = Generator("generator")
    motor = Motor("motor")
    power_bus = PowerBus("bus", num_in=1, num_out=2)
    grid = PowerSink("grid")

    e1 = PowerConnection(turbine, "power", generator, "power_in", label="e1")
    e2 = PowerConnection(generator, "power_out", power_bus, "power_in1", label="e2")
    e3 = PowerConnection(power_bus, "power_out1", motor, "power_in", label="e3")
    e4 = PowerConnection(motor, "power_out", pump, "power", label="e4")
    e5 = PowerConnection(power_bus, "power_out2", grid, "power", label="e5")

    nw.add_conns(e1, e2, e3, e4, e5)

    generator.set_attr(eta=0.98)
    motor.set_attr(eta=0.98)

    a1.set_attr(fluid={"air": 1}, T=200, p=1, m=10)
    b1.set_attr(fluid={"Isopentane": 1}, x=1, T=150)
    b3.set_attr(td_dew=10)
    b4.set_attr(td_bubble=5)
    b7.set_attr(td_bubble=5)
    c1.set_attr(fluid={"air": 1}, T=10, p=1)
    c2.set_attr(T=20)

    recuperator.set_attr(dp1=0, dp2=0)
    condenser.set_attr(dp1=0, dp2=0)
    preheater.set_attr(dp1=0, dp2=0)
    evaporator.set_attr(dp1=0, dp2=0)

    turbine.set_attr(eta_s=0.8)
    pump.set_attr(eta_s=0.7)

    condenser.set_attr(td_pinch=5)
    evaporator.set_attr(td_pinch=10)
    return nw

The model is well specified and solves without any issues:

nw = build_orc()
nw.solve("design")
nw.assert_convergence()
print(f"net power: {nw.get_conn('e5').E.val:.1f} kW")
net power: 178.8 kW

Inspecting a successful solve

Before breaking the model, we look at what the solver does with it when everything works. All inspection methods are available after presolve(), which prepares and presolves the problem but stops before solving.

The original problem holds 57 variables. mass flow, pressure and enthalpy per :code:Connection and an energy flow per :code:PowerConnection. The reduction merges linearly coupled variables (e.g. all mass flows of the cycle, the pressures connected through the :code:dp specifications) and presolves everything the specifications determine directly. 13 variables remain:

nw = build_orc()
nw.presolve("design")
nw.print_variables()
Variables after presolving (13 total):
  #  Property    Represents
---  ----------  --------------------------------------------------------------
  0  h           b6 (h)
  1  h           a2 (h)
  2  h           a3 (h)
  3  h           b2 (h)
  4  h           b3 (h)
  5  E           e5 (E)
  6  h           b4 (h)
  7  h           b5 (h)
  8  m           b3 (m), b4 (m), b2 (m), b1 (m), b5 (m), b6 (m), b7 (m), b8 (m)
  9  m           c1 (m), c2 (m)
 10  p           b3 (p), b4 (p), b2 (p)
 11  E           e1 (E), e2 (E)
 12  E           e3 (E), e4 (E)

Each remaining variable has a short label (e.g. :code:h4) and lists the original variables it represents. The same reduction applies to the equations.

  • :code:nw.print_equations(),

  • :code:nw.print_presolved_variables() and

  • :code:nw.print_presolved_equations() show the tables.

Next, the solver analyses which equation determines which variable and in what order the equations can be processed, see the decomposition section for how this works:

nw.print_blocks()
Block lower triangular decomposition (11 blocks):
  #  Kind    Needs       Equations                                    Variables
---  ------  ----------  -------------------------------------------  -----------
  0  square              condenser.td_pinch, b3.td_dew, b4.td_bubble  h4, h6, p10
  1  scalar              evaporator.td_pinch                          h1
  2  scalar  0           pump.eta_s                                   h7
  3  scalar  0           turbine.eta_s                                h3
  4  scalar  1           evaporator.energy_balance_constraints        m8
  5  scalar  0, 4        condenser.energy_balance_constraints         m9
  6  scalar  0, 2, 4     pump.energy_connector_balance                E12
  7  scalar  0, 2, 3, 4  recuperator.energy_balance_constraints       h0
  8  scalar  3, 4        turbine.energy_connector_balance             E11
  9  scalar  6, 8        bus.energy_balance_constraint                E5
 10  scalar  1, 4, 7     preheater.energy_balance_constraints         h2

The decomposition allows to analyze the problem on the mathematical level. You will find that only the three equations fix the condensation state, i.e. the condenser pinch together with the two saturation offsets. Everything else follows sequentially: The isentropic efficiencies determine the machine outlet enthalpies, the energy balances determine the mass flows and the power connector balances determine the energy flows. The :code:Needs column lists which blocks must be solved before solving a block.

The block lower triangular structure can also be displayed as an incidence matrix, with :code:X marking the entries of an equation within its own block and :code:x marking dependencies on variables of preceding blocks:

nw.print_incidence_matrix(block_order=True)
Incidence matrix:
  Block                                          h4    h6    p10    h1    h7    h3    m8    m9    E12    h0    E11    E5    h2
-------  --------------------------------------  ----  ----  -----  ----  ----  ----  ----  ----  -----  ----  -----  ----  ----
      0  condenser.td_pinch                      X     X     X      -     -     -     -     -     -      -     -      -     -
         b3.td_dew                               X     -     X      -     -     -     -     -     -      -     -      -     -
         b4.td_bubble                            -     X     X      -     -     -     -     -     -      -     -      -     -
      1  evaporator.td_pinch                     -     -     -      X     -     -     -     -     -      -     -      -     -
      2  pump.eta_s                              -     x     x      -     X     -     -     -     -      -     -      -     -
      3  turbine.eta_s                           -     -     x      -     -     X     -     -     -      -     -      -     -
      4  evaporator.energy_balance_constraints   -     -     -      x     -     -     X     -     -      -     -      -     -
      5  condenser.energy_balance_constraints    x     x     -      -     -     -     x     X     -      -     -      -     -
      6  pump.energy_connector_balance           -     x     -      -     x     -     x     -     X      -     -      -     -
      7  recuperator.energy_balance_constraints  x     -     -      -     x     x     x     -     -      X     -      -     -
      8  turbine.energy_connector_balance        -     -     -      -     -     x     x     -     -      -     X      -     -
      9  bus.energy_balance_constraint           -     -     -      -     -     -     -     -     x      -     x      X     -
     10  preheater.energy_balance_constraints    -     -     -      x     -     -     x     -     -      x     -      -     X

The starting values generated for the remaining variables are inspected with :code:print_variable_values. All values are SI values:

nw.print_variable_values()
Current variable values (13 total):
  #  Property    Represents        SI value
---  ----------  ------------  ------------
  0  h           b6 (h)        2.527262e+05
  1  h           a2 (h)        5.909122e+05
  2  h           a3 (h)        5.863523e+05
  3  h           b2 (h)        4.496956e+05
  4  h           b3 (h)        4.629675e+05
  5  E           e5 (E)        2.313834e+04
  6  h           b4 (h)        1.822059e+05
  7  h           b5 (h)        1.962334e+05
  8  m           b3 (m)        5.139894e-01
  8  m           b4 (m)        5.139894e-01
  8  m           b2 (m)        5.139894e-01
  8  m           b1 (m)        5.139894e-01
  8  m           b5 (m)        5.139894e-01
  8  m           b6 (m)        5.139894e-01
  8  m           b7 (m)        5.139894e-01
  8  m           b8 (m)        5.139894e-01
  9  m           c1 (m)        1.067294e+01
  9  m           c2 (m)        1.067294e+01
 10  p           b3 (p)        7.880278e+05
 10  p           b4 (p)        7.880278e+05
 10  p           b2 (p)        7.880278e+05
 11  E           e1 (E)        2.914222e+04
 11  E           e2 (E)        2.855937e+04
 12  E           e3 (E)        5.421034e+03
 12  E           e4 (E)        5.312613e+03

:code:solve_continue runs the solution process on the prepared problem and finishes the calculation:

nw.solve_continue()
print(nw.status)
0

Structural errors

The determination check compares the number of unknowns with the number of equations. With the maximum matching of the decomposition the solver additionally locates the part of the problem in which a defect sits. Specifying the evaporator heat transfer on top of the well determined model will show what equations overdetermine which variable(s).

nw = build_orc()
nw.get_comp("evaporator").set_attr(Q=-1e6)
nw.solve("design")
You have provided too many parameters: 13 required, 14 supplied. Aborting calculation!
The original problem holds 57 variables and 52 equations. Presolving consumed 38 equations and reduced the problem to 13 variables and 14 equations.
Structural analysis - the problem contains an over-determined part (2 equations competing for 1 variable).
Inspect the problem with
  - nw.print_structural_analysis() for the over- or under-determined part of the model,
  - nw.print_variables() and nw.print_equations() for the variables and equations that are present,
  - nw.print_equations_with_dependents() for the variables each equation depends on,
  - nw.print_incidence_matrix() for a compact overview.
---------------------------------------------------------------------------
TESPyNetworkError                         Traceback (most recent call last)
Cell In[9], line 3
      1 nw = build_orc()
      2 nw.get_comp("evaporator").set_attr(Q=-1e6)
----> 3 nw.solve("design")

File ~/checkouts/readthedocs.org/user_builds/tespy/envs/dev/lib/python3.14/site-packages/tespy/networks/network.py:2470, in Network.solve(self, mode, init_path, design_path, max_iter, min_iter, init_only, init_previous, use_cuda, print_results, robust_relax, skip_postprocess, oscillation_damping, block_solve, pause_on_block_failure)
   2374 def solve(self, mode, init_path=None, design_path=None,
   2375           max_iter=50, min_iter=2, init_only=False, init_previous=True,
   2376           use_cuda=False, print_results=True, robust_relax=False, skip_postprocess=False,
   2377           oscillation_damping=False, block_solve=True,
   2378           pause_on_block_failure=False):
   2379     r"""
   2380     Solve the network.
   2381 
   (...)   2468     documentation at tespy.readthedocs.io in the section "TESPy modules".
   2469     """
-> 2470     self.presolve(
   2471         mode, init_path=init_path, design_path=design_path,
   2472         init_previous=init_previous, check=not init_only
   2473     )
   2475     if init_only:
   2476         return

File ~/checkouts/readthedocs.org/user_builds/tespy/envs/dev/lib/python3.14/site-packages/tespy/networks/network.py:2570, in Network.presolve(self, mode, init_path, design_path, init_previous, check)
   2567     return
   2569 try:
-> 2570     self._problem.check_determination()
   2571 except hlp.TESPyNetworkError:
   2572     self.status = self._problem.status

File ~/checkouts/readthedocs.org/user_builds/tespy/envs/dev/lib/python3.14/site-packages/tespy/solver/problem.py:1199, in Problem.check_determination(self)
   1197     logger.error(msg)
   1198     self.status = 12
-> 1199     raise hlp.TESPyNetworkError(msg)
   1200 elif n < self.variable_counter:
   1201     msg = (
   1202         f"You have not provided enough parameters: {self.variable_counter} "
   1203         f"required, {n} supplied. Aborting calculation!{_reduction}"
   1204         f"{self._structural_summary()}{_hint}"
   1205     )

TESPyNetworkError: You have provided too many parameters: 13 required, 14 supplied. Aborting calculation!
The original problem holds 57 variables and 52 equations. Presolving consumed 38 equations and reduced the problem to 13 variables and 14 equations.
Structural analysis - the problem contains an over-determined part (2 equations competing for 1 variable).
Inspect the problem with
  - nw.print_structural_analysis() for the over- or under-determined part of the model,
  - nw.print_variables() and nw.print_equations() for the variables and equations that are present,
  - nw.print_equations_with_dependents() for the variables each equation depends on,
  - nw.print_incidence_matrix() for a compact overview.

The structural analysis remains available after the error is raised and you can print the affected equations and variables:

nw.print_structural_analysis()
Over-determined part - the following equations compete for the involved variables:
  Equations: evaporator.Q, evaporator.td_pinch
  Involved variables: h1

The evaporator heat transfer and its pinch specification both determine the enthalpy at the evaporator air outlet (:code:h1).

Similarly, if we remove a specification from a fresh network instance, we produce an under-determined part:

nw = build_orc()
nw.get_conn("c2").set_attr(T=None)
nw.solve("design")
You have not provided enough parameters: 14 required, 13 supplied. Aborting calculation!
The original problem holds 57 variables and 50 equations. Presolving consumed 37 equations and reduced the problem to 14 variables and 13 equations.
Structural analysis - the problem contains an under-determined part (12 variables constrained by 11 equations).
Inspect the problem with
  - nw.print_structural_analysis() for the over- or under-determined part of the model,
  - nw.print_variables() and nw.print_equations() for the variables and equations that are present,
  - nw.print_equations_with_dependents() for the variables each equation depends on,
  - nw.print_incidence_matrix() for a compact overview.
---------------------------------------------------------------------------
TESPyNetworkError                         Traceback (most recent call last)
Cell In[11], line 3
      1 nw = build_orc()
      2 nw.get_conn("c2").set_attr(T=None)
----> 3 nw.solve("design")

File ~/checkouts/readthedocs.org/user_builds/tespy/envs/dev/lib/python3.14/site-packages/tespy/networks/network.py:2470, in Network.solve(self, mode, init_path, design_path, max_iter, min_iter, init_only, init_previous, use_cuda, print_results, robust_relax, skip_postprocess, oscillation_damping, block_solve, pause_on_block_failure)
   2374 def solve(self, mode, init_path=None, design_path=None,
   2375           max_iter=50, min_iter=2, init_only=False, init_previous=True,
   2376           use_cuda=False, print_results=True, robust_relax=False, skip_postprocess=False,
   2377           oscillation_damping=False, block_solve=True,
   2378           pause_on_block_failure=False):
   2379     r"""
   2380     Solve the network.
   2381 
   (...)   2468     documentation at tespy.readthedocs.io in the section "TESPy modules".
   2469     """
-> 2470     self.presolve(
   2471         mode, init_path=init_path, design_path=design_path,
   2472         init_previous=init_previous, check=not init_only
   2473     )
   2475     if init_only:
   2476         return

File ~/checkouts/readthedocs.org/user_builds/tespy/envs/dev/lib/python3.14/site-packages/tespy/networks/network.py:2570, in Network.presolve(self, mode, init_path, design_path, init_previous, check)
   2567     return
   2569 try:
-> 2570     self._problem.check_determination()
   2571 except hlp.TESPyNetworkError:
   2572     self.status = self._problem.status

File ~/checkouts/readthedocs.org/user_builds/tespy/envs/dev/lib/python3.14/site-packages/tespy/solver/problem.py:1208, in Problem.check_determination(self)
   1206 logger.error(msg)
   1207 self.status = 11
-> 1208 raise hlp.TESPyNetworkError(msg)

TESPyNetworkError: You have not provided enough parameters: 14 required, 13 supplied. Aborting calculation!
The original problem holds 57 variables and 50 equations. Presolving consumed 37 equations and reduced the problem to 14 variables and 13 equations.
Structural analysis - the problem contains an under-determined part (12 variables constrained by 11 equations).
Inspect the problem with
  - nw.print_structural_analysis() for the over- or under-determined part of the model,
  - nw.print_variables() and nw.print_equations() for the variables and equations that are present,
  - nw.print_equations_with_dependents() for the variables each equation depends on,
  - nw.print_incidence_matrix() for a compact overview.
nw.print_structural_analysis()
Under-determined part - the following variables cannot be determined by the involved equations:
  Variables: h0, h2, h3, h4, h5, E6, h7, h8, m10, p11, E12, E13
  Involved equations: bus.energy_balance_constraint, condenser.energy_balance_constraints, condenser.td_pinch, preheater.energy_balance_constraints, pump.energy_connector_balance, pump.eta_s, recuperator.energy_balance_constraints, turbine.energy_connector_balance, turbine.eta_s, b3.td_dew, b4.td_bubble

The list is longer here because the missing equation propagates through multiple parts. without the air outlet temperature the condenser energy balance cannot determine the air mass flow, without the air mass flow the condensation level is open, and so on.

A more subtle case is a count-balanced but structurally singular problem. If we remove the air outlet temperature and specify the evaporator heat transfer, we will see 14 equations for 14 variables. However, now the evaporator side is over-determined and the condenser side is under-determined at the same time. Block-wise solving is impossible in this case and the solver reports the structural analysis and exits with status 3.

nw = build_orc()
nw.get_conn("c2").set_attr(T=None)
nw.get_comp("evaporator").set_attr(Q=-1e6)
nw.solve("design")
print(nw.status)
The problem is structurally singular, block-wise solving is not possible.
Structural analysis - the problem contains an under-determined part (12 variables constrained by 11 equations) and an over-determined part (2 equations competing for 1 variable).
Use nw.print_structural_analysis() for the affected equations and variables. If the structural defect stems from an incomplete dependency declaration of a custom equation, the simultaneous solution can still be attempted with nw.solve(mode, block_solve=False).
3

Both defective parts are visible at once here:

nw.print_structural_analysis()
Under-determined part - the following variables cannot be determined by the involved equations:
  Variables: h0, h2, h3, h4, h5, E6, h7, h8, m10, p11, E12, E13
  Involved equations: bus.energy_balance_constraint, condenser.energy_balance_constraints, condenser.td_pinch, preheater.energy_balance_constraints, pump.energy_connector_balance, pump.eta_s, recuperator.energy_balance_constraints, turbine.energy_connector_balance, turbine.eta_s, b3.td_dew, b4.td_bubble
Over-determined part - the following equations compete for the involved variables:
  Equations: evaporator.Q, evaporator.td_pinch
  Involved variables: h1

Tip

The message also points to :code:block_solve=False to attempt the simultaneous solution anyway, for the case that the structural defect stems from an incomplete dependency declaration of a custom component rather than from the specifications.

The structural analysis works on the incidence only (which equation depends on which variable). A linear dependency can also arise from the values even when the structure is sound, e.g. redundant specifications connected through nonlinear relations or starting values on a saturation plateau. The solver then reports status 3 and the singularity diagnosis names the suspect equations in the log, see the debugging section.

Specifications and decomposition

Changing a specification changes the block structure. In the base model the mass flow of the heat source is given and every block is determined in sequence. If we instead prescribe the net power of the plant, the mass flows can only be determined together with the energy flows through the bus balance:

nw = build_orc()
nw.get_conn("a1").set_attr(m=None)
nw.get_conn("e5").set_attr(E=180)
nw.presolve("design")
nw.print_blocks()
Block lower triangular decomposition (9 blocks):
  #  Kind    Needs       Equations                                                                                       Variables
---  ------  ----------  ----------------------------------------------------------------------------------------------  ------------
  0  square              condenser.td_pinch, b3.td_dew, b4.td_bubble                                                     h4, h5, p9
  1  scalar              evaporator.td_pinch                                                                             h1
  2  scalar  0           pump.eta_s                                                                                      h6
  3  scalar  0           turbine.eta_s                                                                                   h3
  4  square  0, 2, 3     bus.energy_balance_constraint, pump.energy_connector_balance, turbine.energy_connector_balance  m7, E11, E12
  5  scalar  0, 4        condenser.energy_balance_constraints                                                            m8
  6  scalar  1, 4        evaporator.energy_balance_constraints                                                           m10
  7  scalar  0, 2, 3, 4  recuperator.energy_balance_constraints                                                          h0
  8  scalar  1, 4, 6, 7  preheater.energy_balance_constraints                                                            h2

Block 4 now couples the cycle mass flow with the turbine and pump energy flows. The bus balance connects generator output and motor input, which are tied to the same mass flow through the connector balances. The larger the blocks of your model the harder will debugging be. However, especially in offdesign simulations tightly coupled problems that cannot easily be reduced to multiple subproblems are expected.

Staged solving

:code:solve first executes :code:presolve and subsequently :code:solve_continue. You can pause the model execution between the two stages and inspect the problem, the variables and their values and even modify the initial guesses yourself (values are SI values). On this model the generated starting values are good enough that staging is not needed, therefore the cell only intends to show how the mechanism works.

nw = build_orc()
nw.presolve("design")
nw.set_variable_value("b4", "p", 1.2e5)
nw.solve_continue()
print(nw.status)
0

Pausing and inspecting a failed block

This section will show how you can set starting values selectively for a failing block. To construct the example, we first run the simulation with a tiny pinch point value for the condenser, retrieve the resulting UA and impose it to a freshly constructed model. Then we see that the new solve will fail with a non-convergence warning.

nw = build_orc()
nw.get_comp("condenser").set_attr(td_pinch=0.0001)
nw.solve("design")
nw.assert_convergence()
UA_value = nw.get_comp("condenser").UA.val
nw.results["Connection"].loc[["c1", "b3", "b4"], ["m", "p", "h"]]
m p h
c1 78.007313 1.000000 409348.270900
b3 2.078356 0.754166 347635.058524
b4 2.078356 0.754166 -29941.261975
nw = build_orc()
nw.get_comp("condenser").set_attr(td_pinch=None, UA=UA_value)
nw.solve("design", pause_on_block_failure=True)
nw.status
Block 2 did not converge, pausing the solution process.
  Cause: no acceptance within the iteration budget of 50 iterations, the last scaled residual is 4.73e+01
Inspect the failed block with
  - Network.print_blocks() for the block decomposition with the equations, variables and solve status of every block,
  - Network.print_variable_values(block=2) for the variables of the block and their current values,
  - Network.print_block_jacobian(block=2) for the jacobian and residual as evaluated in the failing iteration,
  - Network.print_block_states(block=2) for the states of the involved connections at the current values, with at='failure' as evaluated in the failing iteration.
Modify variable values with Network.set_variable_value and retry with Network.solve_continue(). Passing pause_on_block_failure=False there continues with the standard escalation stages instead in case the block fails again.
20

We can inspect the fluid state at failure, the variables relevant to the block are marked with a star.

nw.print_block_states(block=2, at="failure")
States of the connections of block 2 at the state of failure (*: variable of the block):
Connection    m               p               h                     T  phase
------------  --------------  --------------  --------------  -------  -------
b3            2.078356e+00    3.374839e+06 *  1.389740e+06 *  719.797  g
c1            1.042991e+01 *  1.000000e+05    4.093483e+05    283.15   g
b4            2.078356e+00    3.374839e+06 *  1.389740e+06 *  719.797  g
c2            1.042991e+01 *  1.000000e+05    4.194081e+05    293.15   g
b2            2.078356e+00    3.374839e+06 *  4.496956e+05    456.445  l

And we can inspect the same before starting the block solve. Simultaneously, we can also inspect the variables directly when starting the block solve.

nw.print_block_states(block=2)
nw.print_variable_values(block=2)
States of the connections of block 2 at the current variable values (*: variable of the block):
Connection    m               p               h                    T  phase
------------  --------------  --------------  --------------  ------  -------
b3            2.078356e+00    7.880278e+05 *  4.847008e+05 *  387.25  g
c1            1.883961e+03 *  1.000000e+05    4.093483e+05    283.15  g
b4            2.078356e+00    7.880278e+05 *  1.784017e+05 *  372.25  l
c2            1.883961e+03 *  1.000000e+05    4.194081e+05    293.15  g
b2            2.078356e+00    7.880278e+05 *  4.496956e+05    377.25  tp
Current variable values of block 2 (4 total):
  #  Property    Represents        SI value
---  ----------  ------------  ------------
  4  h           b3 (h)        4.847008e+05
  6  h           b4 (h)        1.784017e+05
  9  m           c1 (m)        1.883961e+03
  9  m           c2 (m)        1.883961e+03
 10  p           b3 (p)        7.880278e+05
 10  p           b4 (p)        7.880278e+05
 10  p           b2 (p)        7.880278e+05

We can transfer the variable values from the tiny pinch solution and continue solving. In this case we apply osciallation_damping to help the convergence, since a tiny change in any of the variables will trigger a sign change in the UA residual.

nw.set_variable_value("b3", "h", 347635)
nw.set_variable_value("c1", "m", 78)
nw.set_variable_value("b4", "h", -29941)
nw.set_variable_value("b3", "p", 754.166)
nw.solve_continue(oscillation_damping=True)
nw.status
Block 2 did not converge, pausing the solution process.
  Cause: no acceptance within the iteration budget of 50 iterations, the last scaled residual is 3.43e-07
Inspect the failed block with
  - Network.print_blocks() for the block decomposition with the equations, variables and solve status of every block,
  - Network.print_variable_values(block=2) for the variables of the block and their current values,
  - Network.print_block_jacobian(block=2) for the jacobian and residual as evaluated in the failing iteration,
  - Network.print_block_states(block=2) for the states of the involved connections at the current values, with at='failure' as evaluated in the failing iteration.
Modify variable values with Network.set_variable_value and retry with Network.solve_continue(). Passing pause_on_block_failure=False there continues with the standard escalation stages instead in case the block fails again.
20

In case you want to continue solving without pausing on a failure, you can run nw.solve_continue(pause_on_block_failure=False).