Repository: Pyomo/pyomo

Issue:
Undesired printed output when using HiGHS solver
## Summary

Whenever I solve any Pyomo model with the HiGHS solver, I always get two lines of 

### Steps to reproduce the issue

Here is a simple minimum working example

```console
import pyomo.environ as pyo
SOLVER = pyo.SolverFactory('appsi_highs')

model = pyo.ConcreteModel()
model.x = pyo.Var(domain=pyo.NonNegativeReals)
model.y = pyo.Var(domain=pyo.NonNegativeReals)
model.obj = pyo.Objective(expr=model.x + model.y, sense=pyo.maximize)
model.c1 = pyo.Constraint(expr=model.x <= 10)
model.c2 = pyo.Constraint(expr=model.y <= 5)
_ = SOLVER.solve(model)
```

### Error Message

Not really an error message, but the undesired output is the following: 

```console
Running HiGHS 1.5.3 [date: ..., git hash: ...]
Copyright (c) 2023 HiGHS under MIT licence terms
```

### Information on your system

Pyomo version: 6.6.2
Python version: 3.11.2
Operating system: macOS
How Pyomo was installed: poetry/pip
Solver (if applicable): Highs 1.5.3

### Additional information

Oddly, when the same exact code is run on Google Colab (after having installed the identical version of Pyomo with the command ```!pip install pyomo```), the output mentioned above does not appear. Note that the Python version running on Google Colab is 3.10.12.
Undesired printed output when using HiGHS solver
## Summary

Whenever I solve any Pyomo model with the HiGHS solver, I always get two lines of 

### Steps to reproduce the issue

Here is a simple minimum working example

```console
import pyomo.environ as pyo
SOLVER = pyo.SolverFactory('appsi_highs')

model = pyo.ConcreteModel()
model.x = pyo.Var(domain=pyo.NonNegativeReals)
model.y = pyo.Var(domain=pyo.NonNegativeReals)
model.obj = pyo.Objective(expr=model.x + model.y, sense=pyo.maximize)
model.c1 = pyo.Constraint(expr=model.x <= 10)
model.c2 = pyo.Constraint(expr=model.y <= 5)
_ = SOLVER.solve(model)
```

### Error Message

Not really an error message, but the undesired output is the following: 

```console
Running HiGHS 1.5.3 [date: ..., git hash: ...]
Copyright (c) 2023 HiGHS under MIT licence terms
```

### Information on your system

Pyomo version: 6.6.2
Python version: 3.11.2
Operating system: macOS
How Pyomo was installed: poetry/pip
Solver (if applicable): Highs 1.5.3

### Additional information

Oddly, when the same exact code is run on Google Colab (after having installed the identical version of Pyomo with the command ```!pip install pyomo```), the output mentioned above does not appear. Note that the Python version running on Google Colab is 3.10.12.


Discussion:

Resolving patch:
diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py
--- a/pyomo/contrib/appsi/solvers/highs.py
+++ b/pyomo/contrib/appsi/solvers/highs.py
@@ -345,15 +345,25 @@ def set_instance(self, model):
                 f'Solver {c.__module__}.{c.__qualname__} is not available '
                 f'({self.available()}).'
             )
-        self._reinit()
-        self._model = model
-        if self.use_extensions and cmodel_available:
-            self._expr_types = cmodel.PyomoExprTypes()
-
-        self._solver_model = highspy.Highs()
-        self.add_block(model)
-        if self._objective is None:
-            self.set_objective(None)
+
+        ostreams = [
+            LogStream(
+                level=self.config.log_level, logger=self.config.solver_output_logger
+            )
+        ]
+        if self.config.stream_solver:
+            ostreams.append(sys.stdout)
+        with TeeStream(*ostreams) as t:
+            with capture_output(output=t.STDOUT, capture_fd=True):
+                self._reinit()
+                self._model = model
+                if self.use_extensions and cmodel_available:
+                    self._expr_types = cmodel.PyomoExprTypes()
+
+                self._solver_model = highspy.Highs()
+                self.add_block(model)
+                if self._objective is None:
+                    self.set_objective(None)
 
     def _add_constraints(self, cons: List[_GeneralConstraintData]):
         self._sol = None


Test patch:
diff --git a/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py
--- a/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py
+++ b/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py
@@ -1,5 +1,11 @@
+import subprocess
+import sys
+
 import pyomo.common.unittest as unittest
 import pyomo.environ as pe
+
+from pyomo.common.log import LoggingIntercept
+from pyomo.common.tee import capture_output
 from pyomo.contrib.appsi.solvers.highs import Highs
 from pyomo.contrib.appsi.base import TerminationCondition
 
@@ -62,3 +68,42 @@ def test_mutable_params_with_remove_vars(self):
         m.p2.value = 9
         res = opt.solve(m)
         self.assertAlmostEqual(res.best_feasible_objective, -9)
+
+    def test_capture_highs_output(self):
+        # tests issue #3003
+        #
+        # Note that the "Running HiGHS" message is only emitted the
+        # first time that a model is instantiated.  We need to test this
+        # in a subprocess to trigger that output.
+        model = [
+            'import pyomo.environ as pe',
+            'm = pe.ConcreteModel()',
+            'm.x = pe.Var(domain=pe.NonNegativeReals)',
+            'm.y = pe.Var(domain=pe.NonNegativeReals)',
+            'm.obj = pe.Objective(expr=m.x + m.y, sense=pe.maximize)',
+            'm.c1 = pe.Constraint(expr=m.x <= 10)',
+            'm.c2 = pe.Constraint(expr=m.y <= 5)',
+            'from pyomo.contrib.appsi.solvers.highs import Highs',
+            'result = Highs().solve(m)',
+            'print(m.x.value, m.y.value)',
+        ]
+
+        with LoggingIntercept() as LOG, capture_output(capture_fd=True) as OUT:
+            subprocess.run([sys.executable, '-c', ';'.join(model)])
+        self.assertEqual(LOG.getvalue(), "")
+        self.assertEqual(OUT.getvalue(), "10.0 5.0\n")
+
+        model[-2:-1] = [
+            'opt = Highs()',
+            'opt.config.stream_solver = True',
+            'result = opt.solve(m)',
+        ]
+        with LoggingIntercept() as LOG, capture_output(capture_fd=True) as OUT:
+            subprocess.run([sys.executable, '-c', ';'.join(model)])
+        self.assertEqual(LOG.getvalue(), "")
+        # This is emitted by the model set-up
+        self.assertIn("Running HiGHS", OUT.getvalue())
+        # This is emitted by the solve()
+        self.assertIn("HiGHS run time", OUT.getvalue())
+        ref = "10.0 5.0\n"
+        self.assertEqual(ref, OUT.getvalue()[-len(ref) :])