Skip to content

Commit 5d40c36

Browse files
committed
Add action to collect and update info
1 parent 8cdb8f0 commit 5d40c36

7 files changed

Lines changed: 2062 additions & 0 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
numpy
2+
scipy
3+
pandas
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
"""
2+
s_getInfo.py - Collect problem information from S2MPJ Python problem set
3+
4+
This script scans all problems in the S2MPJ Python collection and extracts
5+
various metrics including dimensions, constraint counts, and function values.
6+
The results are saved to CSV files for later use by OptiProfiler.
7+
8+
Usage: python s_getInfo.py
9+
"""
10+
11+
import numpy as np
12+
import pandas as pd
13+
import signal
14+
import os
15+
import sys
16+
17+
# Add the repository root to path so we can import s2mpj_tools
18+
cwd = os.path.dirname(os.path.abspath(__file__))
19+
repo_root = os.path.abspath(os.path.join(cwd, '..', '..', '..'))
20+
sys.path.insert(0, repo_root)
21+
22+
from s2mpj_tools import s2mpj_load
23+
24+
# Set the timeout (seconds) for each problem to be loaded
25+
timeout = 50
26+
27+
# Read problem list from src directory
28+
filename = os.path.join(repo_root, 'src', 'list_of_python_problems')
29+
with open(filename, 'r') as file:
30+
problem_names = [line.strip().replace('.py', '') for line in file.readlines()
31+
if line.strip() and not line.startswith('#')]
32+
33+
# Exclude problematic problems that are known to cause issues
34+
problem_exclude = [
35+
'SPARCO10LS', 'SPARCO10', 'SPARCO11LS', 'SPARCO11', 'SPARCO12LS', 'SPARCO12',
36+
'SPARCO2LS', 'SPARCO2', 'SPARCO3LS', 'SPARCO3', 'SPARCO5LS', 'SPARCO5',
37+
'SPARCO7LS', 'SPARCO7', 'SPARCO8LS', 'SPARCO8', 'SPARCO9LS', 'SPARCO9',
38+
'ROSSIMP3_mp', 'HS67', 'HS68', 'HS69', 'HS85', 'HS88', 'HS89', 'HS90',
39+
'HS91', 'HS92', 'TWIRIBG1'
40+
]
41+
problem_names = [name for name in problem_names if name not in problem_exclude]
42+
43+
# List of known feasibility problems (objective function is not meaningful)
44+
known_feasibility = [
45+
'AIRCRFTA', 'ARGAUSS', 'ARGLALE', 'ARGLBLE', 'ARGTRIG', 'ARTIF', 'BAmL1SP',
46+
'BARDNE', 'BEALENE', 'BENNETT5', 'BIGGS6NE', 'BOOTH', 'BOXBOD', 'BRATU2D',
47+
'BRATU2DT', 'BRATU3D', 'BROWNBSNE', 'BROWNDENE', 'BROYDN3D', 'CBRATU2D',
48+
'CBRATU3D', 'CHANDHEQ', 'CHEMRCTA', 'CHWIRUT2', 'CLUSTER', 'COOLHANS',
49+
'CUBENE', 'CYCLIC3', 'CYCLOOCF', 'CYCLOOCT', 'DANIWOOD', 'DANWOOD',
50+
'DECONVBNE', 'DENSCHNBNE', 'DENSCHNDNE', 'DENSCHNFNE', 'DEVGLA1NE',
51+
'DEVGLA2NE', 'DRCAVTY1', 'DRCAVTY2', 'DRCAVTY3', 'ECKERLE4', 'EGGCRATENE',
52+
'EIGENA', 'EIGENB', 'ELATVIDUNE', 'ENGVAL2NE', 'ENSO', 'ERRINROSNE',
53+
'ERRINRSMNE', 'EXP2NE', 'EXTROSNBNE', 'FLOSP2HH', 'FLOSP2HL', 'FLOSP2HM',
54+
'FLOSP2TH', 'FLOSP2TL', 'FLOSP2TM', 'FREURONE', 'GENROSEBNE', 'GOTTFR',
55+
'GROWTH', 'GULFNE', 'HAHN1', 'HATFLDANE', 'HATFLDBNE', 'HATFLDCNE',
56+
'HATFLDDNE', 'HATFLDENE', 'HATFLDFLNE', 'HATFLDF', 'HATFLDG', 'HELIXNE',
57+
'HIMMELBA', 'HIMMELBC', 'HIMMELBD', 'HIMMELBFNE', 'HS1NE', 'HS25NE',
58+
'HS2NE', 'HS8', 'HYDCAR20', 'HYDCAR6', 'HYPCIR', 'INTEGREQ', 'INTEQNE',
59+
'KOEBHELBNE', 'KOWOSBNE', 'KSS', 'LANCZOS1', 'LANCZOS2', 'LANCZOS3',
60+
'LEVYMONE10', 'LEVYMONE5', 'LEVYMONE6', 'LEVYMONE7', 'LEVYMONE8',
61+
'LEVYMONE9', 'LEVYMONE', 'LIARWHDNE', 'LINVERSENE', 'LSC1', 'LSC2',
62+
'LUKSAN11', 'LUKSAN12', 'LUKSAN13', 'LUKSAN14', 'LUKSAN17', 'LUKSAN21',
63+
'LUKSAN22', 'MANCINONE', 'METHANB8', 'METHANL8', 'MEYER3NE', 'MGH09',
64+
'MGH10', 'MISRA1A', 'MISRA1B', 'MISRA1C', 'MISRA1D', 'MODBEALENE',
65+
'MSQRTA', 'MSQRTB', 'MUONSINE', 'n10FOLDTR', 'NELSON', 'NONSCOMPNE',
66+
'NYSTROM5', 'OSBORNE1', 'OSBORNE2', 'OSCIGRNE', 'OSCIPANE', 'PALMER1ANE',
67+
'PALMER1BNE', 'PALMER1ENE', 'PALMER1NE', 'PALMER2ANE', 'PALMER2BNE',
68+
'PALMER2ENE', 'PALMER3ANE', 'PALMER3BNE', 'PALMER3ENE', 'PALMER4ANE',
69+
'PALMER4BNE', 'PALMER4ENE', 'PALMER5ANE', 'PALMER5BNE', 'PALMER5ENE',
70+
'PALMER6ANE', 'PALMER6ENE', 'PALMER7ANE', 'PALMER7ENE', 'PALMER8ANE',
71+
'PALMER8ENE', 'PENLT1NE', 'PENLT2NE', 'POROUS1', 'POROUS2', 'POWELLBS',
72+
'POWELLSQ', 'POWERSUMNE', 'PRICE3NE', 'PRICE4NE', 'QINGNE', 'QR3D',
73+
'RAT42', 'RAT43', 'RECIPE', 'REPEAT', 'RES', 'ROSZMAN1', 'RSNBRNE',
74+
'SANTA', 'SEMICN2U', 'SEMICON1', 'SEMICON2', 'SPECANNE', 'SSBRYBNDNE',
75+
'SSINE', 'THURBER', 'TQUARTICNE', 'VANDERM1', 'VANDERM2', 'VANDERM3',
76+
'VANDERM4', 'VARDIMNE', 'VESUVIA', 'VESUVIO', 'VESUVIOU', 'VIBRBEAMNE',
77+
'WATSONNE', 'WAYSEA1NE', 'WAYSEA2NE', 'YATP1CNE', 'YATP2CNE', 'YFITNE',
78+
'ZANGWIL3'
79+
]
80+
81+
# To store all the feasibility problems discovered during runtime
82+
feasibility = []
83+
84+
# To store all the 'time out' problems
85+
timeout_problems = []
86+
87+
# Output path (repository root)
88+
saving_path = repo_root
89+
90+
91+
class Logger:
92+
"""Dual-output logger that writes to both terminal and log file."""
93+
def __init__(self, logfile):
94+
self.terminal = sys.__stdout__
95+
self.log = logfile
96+
97+
def write(self, message):
98+
self.terminal.write(message)
99+
try:
100+
self.log.write(message)
101+
except Exception as e:
102+
self.terminal.write(f"[Logger Error] {e}\n")
103+
104+
def flush(self):
105+
self.terminal.flush()
106+
self.log.flush()
107+
108+
109+
def run_with_timeout(func, args, timeout_seconds):
110+
"""Execute a function with a timeout using SIGALRM."""
111+
def handler(signum, frame):
112+
raise TimeoutError(f"Function timed out after {timeout_seconds} seconds")
113+
114+
signal.signal(signal.SIGALRM, handler)
115+
signal.alarm(timeout_seconds)
116+
117+
try:
118+
result = func(*args) if args else func()
119+
return result
120+
finally:
121+
signal.alarm(0)
122+
123+
124+
def get_problem_info(problem_name, known_feasibility):
125+
"""
126+
Extract information about a single problem.
127+
128+
Returns a dictionary containing problem metrics such as dimensions,
129+
constraint counts, and function availability flags.
130+
"""
131+
print(f"Processing problem: {problem_name}")
132+
133+
info_single = {
134+
'problem_name': problem_name,
135+
'ptype': 'unknown',
136+
'xtype': 'unknown',
137+
'dim': 'unknown',
138+
'mb': 'unknown',
139+
'ml': 'unknown',
140+
'mu': 'unknown',
141+
'mcon': 'unknown',
142+
'mlcon': 'unknown',
143+
'mnlcon': 'unknown',
144+
'm_ub': 'unknown',
145+
'm_eq': 'unknown',
146+
'm_linear_ub': 'unknown',
147+
'm_linear_eq': 'unknown',
148+
'm_nonlinear_ub': 'unknown',
149+
'm_nonlinear_eq': 'unknown',
150+
'f0': 0,
151+
'isfeasibility': 1,
152+
'isgrad': 0,
153+
'ishess': 0,
154+
'isjcub': 0,
155+
'isjceq': 0,
156+
'ishcub': 0,
157+
'ishceq': 0
158+
}
159+
160+
# Try to load the problem with timeout protection
161+
try:
162+
p = run_with_timeout(s2mpj_load, (problem_name,), timeout)
163+
except TimeoutError:
164+
print(f"Timeout while loading problem {problem_name}.")
165+
timeout_problems.append(problem_name)
166+
return info_single
167+
168+
# Extract basic problem information
169+
try:
170+
info_single['ptype'] = p.ptype
171+
info_single['xtype'] = 'r'
172+
info_single['dim'] = p.n
173+
info_single['mb'] = p.mb
174+
info_single['ml'] = sum(p.xl > -np.inf)
175+
info_single['mu'] = sum(p.xu < np.inf)
176+
info_single['mcon'] = p.mcon
177+
info_single['mlcon'] = p.mlcon
178+
info_single['mnlcon'] = p.mnlcon
179+
info_single['m_ub'] = p.m_linear_ub + p.m_nonlinear_ub
180+
info_single['m_eq'] = p.m_linear_eq + p.m_nonlinear_eq
181+
info_single['m_linear_ub'] = p.m_linear_ub
182+
info_single['m_linear_eq'] = p.m_linear_eq
183+
info_single['m_nonlinear_ub'] = p.m_nonlinear_ub
184+
info_single['m_nonlinear_eq'] = p.m_nonlinear_eq
185+
except Exception as e:
186+
print(f"Error while getting problem info for {problem_name}: {e}")
187+
188+
# Evaluate the objective function to determine if it's a feasibility problem
189+
try:
190+
f = run_with_timeout(p.fun, (p.x0,), timeout)
191+
if problem_name == 'LIN':
192+
info_single['isfeasibility'] = 0
193+
info_single['f0'] = np.nan
194+
elif np.size(f) == 0 or np.isnan(f) or problem_name in known_feasibility:
195+
info_single['isfeasibility'] = 1
196+
info_single['f0'] = 0
197+
feasibility.append(problem_name)
198+
else:
199+
info_single['isfeasibility'] = 0
200+
info_single['f0'] = f
201+
except Exception as e:
202+
print(f"Error while evaluating function for {problem_name}: {e}")
203+
info_single['f0'] = 0
204+
info_single['isfeasibility'] = 1
205+
feasibility.append(problem_name)
206+
207+
# Check availability of gradient, Hessian, and constraint Jacobians/Hessians
208+
if problem_name in feasibility:
209+
info_single['isgrad'] = 1
210+
info_single['ishess'] = 1
211+
else:
212+
try:
213+
g = run_with_timeout(p.grad, (p.x0,), timeout)
214+
info_single['isgrad'] = 1 if g.size > 0 else 0
215+
except Exception:
216+
info_single['isgrad'] = 0
217+
218+
try:
219+
h = run_with_timeout(p.hess, (p.x0,), timeout)
220+
info_single['ishess'] = 1 if h.size > 0 else 0
221+
except Exception:
222+
info_single['ishess'] = 0
223+
224+
try:
225+
jc = run_with_timeout(p.jcub, (p.x0,), timeout)
226+
info_single['isjcub'] = 1 if jc.size > 0 else 0
227+
except Exception:
228+
info_single['isjcub'] = 0
229+
230+
try:
231+
jc = run_with_timeout(p.jceq, (p.x0,), timeout)
232+
info_single['isjceq'] = 1 if jc.size > 0 else 0
233+
except Exception:
234+
info_single['isjceq'] = 0
235+
236+
try:
237+
hc = run_with_timeout(p.hcub, (p.x0,), timeout)
238+
info_single['ishcub'] = 1 if len(hc) > 0 else 0
239+
except Exception:
240+
info_single['ishcub'] = 0
241+
242+
try:
243+
hc = run_with_timeout(p.hceq, (p.x0,), timeout)
244+
info_single['ishceq'] = 1 if len(hc) > 0 else 0
245+
except Exception:
246+
info_single['ishceq'] = 0
247+
248+
print(f"Finished processing problem {problem_name}.")
249+
return info_single
250+
251+
252+
if __name__ == "__main__":
253+
# Set up logging to both terminal and file
254+
log_file = open(os.path.join(saving_path, 'log_python.txt'), 'w')
255+
sys.stdout = Logger(log_file)
256+
sys.stderr = Logger(log_file)
257+
258+
# Process all problems and collect their information
259+
results = []
260+
for name in problem_names:
261+
info = get_problem_info(name, known_feasibility)
262+
results.append(info)
263+
sys.stdout.flush()
264+
sys.stderr.flush()
265+
266+
# Create DataFrame and filter out problems with 'unknown' values
267+
df = pd.DataFrame(results)
268+
269+
def has_unknown_values(row):
270+
for value in row:
271+
if str(value).strip().lower() == 'unknown':
272+
return True
273+
return False
274+
275+
unknown_mask = df.apply(has_unknown_values, axis=1)
276+
if unknown_mask.any():
277+
filtered_problems = df.loc[unknown_mask, 'problem_name'].tolist()
278+
print(f"Filtered out {len(filtered_problems)} problems with 'unknown' values:")
279+
for problem in filtered_problems:
280+
print(f" - {problem}")
281+
282+
df_clean = df[~unknown_mask]
283+
284+
# Save results to CSV
285+
df_clean.to_csv(os.path.join(saving_path, 'probinfo_python.csv'), index=False, na_rep='nan')
286+
287+
# Save feasibility problems list
288+
with open(os.path.join(saving_path, 'feasibility_python.txt'), 'w') as f:
289+
f.write(' '.join(feasibility))
290+
291+
# Save timeout problems list
292+
with open(os.path.join(saving_path, 'timeout_problems_python.txt'), 'w') as f:
293+
f.write(' '.join(timeout_problems))
294+
295+
print("Script completed successfully.")
296+
297+
# Clean up logging
298+
log_file.close()
299+
sys.stdout = sys.__stdout__
300+
sys.stderr = sys.__stderr__

.github/workflows/collect_info.yml

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
name: Collect Problem Info
2+
3+
# This workflow collects problem information from the S2MPJ Python problem set.
4+
# It extracts metrics like dimensions, constraint counts, and function values,
5+
# then saves the results to probinfo_python.csv in the repository root.
6+
7+
on:
8+
# Run after the sync workflow completes to ensure we have the latest problems
9+
workflow_run:
10+
workflows: ["Sync S2MPJ Python Subset"]
11+
types:
12+
- completed
13+
# Allow manual triggering from the Actions tab
14+
workflow_dispatch:
15+
16+
permissions:
17+
contents: write
18+
19+
jobs:
20+
collect-info:
21+
runs-on: ubuntu-latest
22+
timeout-minutes: 360
23+
24+
steps:
25+
# Step 1: Checkout the repository
26+
- name: Checkout repository
27+
uses: actions/checkout@v4
28+
29+
# Step 2: Set up Python environment
30+
- name: Setup Python
31+
uses: actions/setup-python@v5
32+
with:
33+
python-version: '3.11'
34+
check-latest: true
35+
36+
# Step 3: Install dependencies
37+
- name: Install Python dependencies
38+
run: |
39+
python -m pip install --upgrade pip
40+
pip install -r .github/actions/collect_info/requirements.txt
41+
42+
# Step 4: Run the info collection script
43+
- name: Collect problem information
44+
run: python .github/actions/collect_info/s_getInfo.py
45+
46+
# Step 5: Configure git for committing
47+
- name: Configure Git Identity
48+
run: |
49+
git config --global user.name 'github-actions[bot]'
50+
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
51+
52+
# Step 6: Commit and push the updated CSV if there are changes
53+
- name: Commit and push changes
54+
run: |
55+
git add probinfo_python.csv feasibility_python.txt timeout_problems_python.txt log_python.txt
56+
57+
if git diff --staged --quiet; then
58+
echo "No changes detected. The problem info is up to date."
59+
else
60+
echo "Updates detected. Committing changes..."
61+
git commit -m "Auto-update problem info from S2MPJ Python"
62+
git push
63+
fi
64+
65+
# Step 7: Upload artifacts for debugging/archival
66+
- name: Upload artifacts
67+
uses: actions/upload-artifact@v4
68+
with:
69+
name: probinfo-files-python
70+
path: |
71+
probinfo_python.csv
72+
feasibility_python.txt
73+
timeout_problems_python.txt
74+
log_python.txt

__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .s2mpj_tools import s2mpj_load, s2mpj_select

0 commit comments

Comments
 (0)