Repository: Pyomo/pyomo

Issue:
cpxlp writer: don't sort terms when file_determinism=0
## Summary

Currently, the `cpxlp` problem writer sorts the terms of expressions. I suggest disabling this when the user specifies `file_determinism=0`

### Rationale

Sorting is slow! With a medium size model, 10% of the time to write the .lp file is spent doing the sort in question.

![image](https://user-images.githubusercontent.com/13053829/197849430-441fbe13-5f0b-4379-96fc-6cee9307f4d0.png)


### Description

These are the lines of code in question.

https://github.com/Pyomo/pyomo/blob/98697d529370dbf3e3ed367bb8fd3e1fa94c0077/pyomo/repn/plugins/cpxlp.py#L209-L216


### Additional information

I will be preparing a pull request shortly.


Discussion:

Resolving patch:
diff --git a/pyomo/repn/plugins/cpxlp.py b/pyomo/repn/plugins/cpxlp.py
--- a/pyomo/repn/plugins/cpxlp.py
+++ b/pyomo/repn/plugins/cpxlp.py
@@ -181,6 +181,7 @@ def _print_expr_canonical(self,
                               variable_symbol_dictionary,
                               is_objective,
                               column_order,
+                              file_determinism,
                               force_objective_constant=False):
 
         """
@@ -211,9 +212,13 @@ def _print_expr_canonical(self,
                 # Order columns by dictionary names
                 #
                 names = [variable_symbol_dictionary[id(var)] for var in x.linear_vars]
-                    
-                for i, name in sorted(enumerate(names), key=lambda x: x[1]):
-                    output.append(linear_coef_string_template % (x.linear_coefs[i], name))
+
+                term_iterator = zip(x.linear_coefs, names)
+                if file_determinism > 0:
+                    term_iterator = sorted(term_iterator, key=lambda x: x[1])
+
+                for coef, name in term_iterator:
+                    output.append(linear_coef_string_template % (coef, name))
             else:
                 #
                 # Order columns by the value of column_order[]
@@ -221,6 +226,7 @@ def _print_expr_canonical(self,
                 for i, var in sorted(enumerate(x.linear_vars), key=lambda x: column_order[x[1]]):
                     name = variable_symbol_dictionary[id(var)]
                     output.append(linear_coef_string_template % (x.linear_coefs[i], name))
+
         #
         # Quadratic
         #
@@ -250,7 +256,11 @@ def _print_expr_canonical(self,
                         quad.add(i)
                         names.append( (name1,name1) )
                     i += 1
-                for i, names_ in sorted(enumerate(names), key=lambda x: x[1]):
+
+                term_iterator = enumerate(names)
+                if file_determinism > 0:
+                    term_iterator = sorted(term_iterator, key=lambda x: x[1])
+                for i, names_ in term_iterator:
                     #
                     # Times 2 because LP format requires /2 for all the quadratic
                     # terms /of the objective only/.  Discovered the last bit thru
@@ -466,6 +476,7 @@ def print_expr_canonical(
                 variable_symbol_dictionary,
                 is_objective,
                 column_order,
+                file_determinism,
                 force_objective_constant=False):
             try:
                 return self._print_expr_canonical(
@@ -475,6 +486,7 @@ def print_expr_canonical(
                     variable_symbol_dictionary=variable_symbol_dictionary,
                     is_objective=is_objective,
                     column_order=column_order,
+                    file_determinism=file_determinism,
                     force_objective_constant=force_objective_constant)
             except KeyError as e:
                 _id = e.args[0]
@@ -586,6 +598,7 @@ def print_expr_canonical(
                     variable_symbol_dictionary,
                     True,
                     column_order,
+                    file_determinism,
                     force_objective_constant=force_objective_constant)
 
         if numObj == 0:
@@ -697,7 +710,8 @@ def yield_all_constraints():
                     object_symbol_dictionary,
                     variable_symbol_dictionary,
                     False,
-                    column_order
+                    column_order,
+                    file_determinism
                 )
                 bound = constraint_data.lower
                 bound = _get_bound(bound) - offset
@@ -720,7 +734,8 @@ def yield_all_constraints():
                         object_symbol_dictionary,
                         variable_symbol_dictionary,
                         False,
-                        column_order
+                        column_order,
+                        file_determinism
                     )
                     bound = constraint_data.lower
                     bound = _get_bound(bound) - offset
@@ -744,7 +759,8 @@ def yield_all_constraints():
                         object_symbol_dictionary,
                         variable_symbol_dictionary,
                         False,
-                        column_order
+                        column_order,
+                        file_determinism
                     )
                     bound = constraint_data.upper
                     bound = _get_bound(bound) - offset


Test patch: