solver-support 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,168 @@
1
+ """
2
+ UPF solver implementation - the single class behind every UPF-routed model.
3
+
4
+ UpfPlanSolver drives the consumer's UPF planner CLI through run_phase, so it
5
+ inherits the timeout/signal handling and SIGTERM summary generation every solver
6
+ gets. The CLI name is not baked in: the consumer supplies it (``upfplan_command=``
7
+ or by overriding the ``UPFPLAN_COMMAND`` class attribute). It reaches a UPF engine
8
+ by the two routes that CLI supports, selected here by ``build_mode``:
9
+
10
+ - build_mode is None (PDDL models, e.g. pddlnoaxiom): pass ``--domain`` so the
11
+ CLI reads the PDDL through unified_planning's PDDLReader and solves with the
12
+ engine chosen by the effective ``--tune`` (enhsp-opt / fast-downward / symk;
13
+ see ModelRegistry.get_upf_engine). The same class therefore backs the upfenhsp,
14
+ upffd and upfsymk solvers. The CLI streams that engine's progress live,
15
+ so run_phase tees it to the terminal the way fd/symk do.
16
+
17
+ - build_mode is 'upf'/'upflocal' (constructed models): pass ``--build`` so the
18
+ CLI builds the Problem in Python and solves it with the tune's engine (Fast
19
+ Downward blind by default; upfenhsp defaults to the metric-optimal enhsp-opt).
20
+ This replaces the former UPFSolver/UPFLocalSolver classes; that path buffers the
21
+ engine output (it surfaces only once solving finishes) and emits a scored plan.
22
+
23
+ Optimality is a ``--tune`` dial, not a solver name: every upf* solver optimises the
24
+ score by default (upffd/upfsymk are cost-optimal by construction, upfenhsp defaults
25
+ to enhsp-opt), and satisficing ENHSP is opt-in via ``--tune satisficing``.
26
+ """
27
+
28
+ from pathlib import Path
29
+ from typing import Optional
30
+
31
+ from .solver_base import Solver
32
+ from .global_state import global_state
33
+
34
+
35
+ class UpfPlanSolver(Solver):
36
+ """Solver that runs a model through a UPF engine (PDDL or constructed)."""
37
+
38
+ # CLI entry point that does the UPF PDDLReader/construction + OneshotPlanner
39
+ # solve. No generic default (the CLI is consumer-provided); a consumer sets it
40
+ # per instance via ``upfplan_command=`` or by overriding this class attribute.
41
+ UPFPLAN_COMMAND: Optional[str] = None
42
+
43
+ def __init__(self, solver_runner, solver_name: str = "upfenhsp",
44
+ build_mode: Optional[str] = None,
45
+ upfplan_command: Optional[str] = None):
46
+ super().__init__(solver_runner)
47
+ self.solver_name = solver_name
48
+ # None -> read PDDL via --domain; 'upf'/'upflocal' -> construct via --build.
49
+ self.build_mode = build_mode
50
+ # The consumer's UPF planner CLI; an explicit argument wins, else the
51
+ # class-attribute override, else unset.
52
+ self.upfplan_command = upfplan_command or self.UPFPLAN_COMMAND
53
+
54
+ def _tune_fd_search(self, registry, tune, engine_name) -> Optional[str]:
55
+ """FD search config for the active tune - only for fast-downward engines
56
+ (enhsp/symk ignore it, and get_upf_fd_search returns None for them anyway)."""
57
+ if not (engine_name and engine_name.startswith('fast-downward')):
58
+ return None
59
+ try:
60
+ return registry.get_upf_fd_search(self.solver_name, tune)
61
+ except Exception:
62
+ return None
63
+
64
+ def solve(self, param_file: Path, **kwargs) -> Optional[Path]:
65
+ """
66
+ Solve the problem in ``param_file`` through a UPF engine.
67
+
68
+ Args:
69
+ param_file: generated PDDL problem (PDDL models) or .upf instance
70
+ (constructed upf/upflocal models)
71
+ **kwargs: model_path (PDDL domain) - required unless build_mode is set
72
+
73
+ Returns:
74
+ Path to the solution file if a plan was found, else None.
75
+ """
76
+ # Effective --tune: an explicit --tune wins, else the model's pinned default.
77
+ # It selects the UPF engine (the optimality mode) the way it selects
78
+ # savilerow's sat_args or the direct enhsp -planner (see get_upf_engine).
79
+ # upfenhsp defaults to the metric-optimal engine so a plain run maximises the
80
+ # score, matching the cost-optimal upffd/upfsymk; --tune satisficing opts out.
81
+ if not self.upfplan_command:
82
+ raise ValueError(
83
+ "no UPF planner command configured; pass upfplan_command= to "
84
+ "UpfPlanSolver or set its UPFPLAN_COMMAND to the consumer's UPF CLI")
85
+
86
+ registry = global_state.model_registry
87
+ tune = self._effective_tune()
88
+
89
+ if self.build_mode:
90
+ # Constructed models build the Problem in Python (no PDDL domain). The
91
+ # engine comes from the effective tune, so the axiom-free builds
92
+ # (upflocalnoaxiom) can be solved by an axiom-rejecting engine like ENHSP,
93
+ # not just Fast Downward.
94
+ try:
95
+ engine_name = registry.get_upf_engine(self.solver_name, tune)
96
+ except Exception:
97
+ engine_name = None
98
+ engine_name = engine_name or 'fast-downward'
99
+ cmd = [self.upfplan_command, "--build", self.build_mode,
100
+ "-s", engine_name, str(param_file)]
101
+ # For the fast-downward engine the tune also picks the FD search config
102
+ # (upffd: optimal blind vs satisficing greedy); forward it so --build
103
+ # honours --tune instead of the search compile_and_solve would pin.
104
+ fd_search = self._tune_fd_search(registry, tune, engine_name)
105
+ if fd_search:
106
+ cmd += ["--fd-search", fd_search]
107
+ self.logger.info(f"Building UPF model '{self.build_mode}' (engine: {engine_name})")
108
+ else:
109
+ model_path = kwargs.get('model_path')
110
+ if not model_path:
111
+ raise ValueError(
112
+ "model_path (PDDL domain) is required for the upfplan solver")
113
+
114
+ # UPF engine name comes from the effective tune (upfenhsp: optimal ->
115
+ # enhsp-opt by default, satisficing -> enhsp, anytime -> enhsp-any),
116
+ # defaulting to the bare 'enhsp' engine if the solver defines none.
117
+ try:
118
+ engine_name = registry.get_upf_engine(self.solver_name, tune)
119
+ except Exception:
120
+ engine_name = None
121
+ engine_name = engine_name or 'enhsp'
122
+
123
+ cmd = [
124
+ self.upfplan_command,
125
+ "--domain", str(model_path),
126
+ "--engine", engine_name,
127
+ ]
128
+ # Optional fast-downward search config from the active tune (upffd: optimal
129
+ # blind vs satisficing greedy), so the upffd config both measures pure UPF
130
+ # overhead against the direct fd path (blind) and exposes the search dial.
131
+ fd_search = self._tune_fd_search(registry, tune, engine_name)
132
+ if fd_search:
133
+ cmd += ["--fd-search", fd_search]
134
+ cmd.append(str(param_file))
135
+
136
+ self.logger.info(f"Running UPF engine '{engine_name}'")
137
+
138
+ # Forward debug so the upfplan subprocess's diagnostics (the compilation
139
+ # trace, the dumped compiled problem) print on its stderr under the
140
+ # consumer's debug flag (its `-d` / `--debug`) - where run_phase
141
+ # captures/tees them, and clear of the plan-only stdout the .solution is
142
+ # built from.
143
+ if global_state.debug:
144
+ cmd.append("--debug")
145
+
146
+ result = self.runner.run_phase("solving", cmd, expected_outputs=[])
147
+
148
+ if result.interrupted:
149
+ self.logger.warning("upfplan solver was interrupted")
150
+ return None
151
+
152
+ # Timeout with no plan is a timeout (mirrors UPF/pddl handling).
153
+ self._handle_timeout_result(result, "upfplan solver")
154
+
155
+ if result.success and result.stdout and result.stdout.strip():
156
+ solution_file = self._generate_solution_file_path()
157
+ solution_file.write_text(result.stdout)
158
+ if self.runner.summary.phases:
159
+ self.runner.summary.phases[-1].output_files = [str(solution_file)]
160
+ self.logger.info(f"upfplan solution written to {solution_file}")
161
+ return solution_file
162
+
163
+ if result.success:
164
+ self.logger.warning("upfplan solver produced no plan")
165
+ return None
166
+
167
+ self.logger.warning(f"upfplan solver failed: {result.stderr}")
168
+ return None
@@ -0,0 +1,146 @@
1
+ '''
2
+ utility functions
3
+ '''
4
+
5
+ import sys
6
+ import logging
7
+ from datetime import datetime
8
+ from typing import Optional
9
+ from pathlib import Path
10
+
11
+ # Logger name that warn() routes through. Defaults to the package logger so the
12
+ # core is usable standalone; a consumer that configures its own named logger (with
13
+ # file/console handlers) can point warn() at it via set_warn_logger_name() so these
14
+ # warnings share the consumer's handlers. Read at call time, so wiring may happen
15
+ # any time before the first warn().
16
+ _warn_logger_name = 'solver_support'
17
+
18
+
19
+ def set_warn_logger_name(name: str) -> None:
20
+ """Route warn() through the logger called ``name``.
21
+
22
+ Consumers call this once (e.g. at import) to funnel core warnings into their
23
+ own configured logger, preserving pre-extraction behaviour where warnings
24
+ reached the runner's log file/console handlers.
25
+ """
26
+ global _warn_logger_name
27
+ _warn_logger_name = name
28
+
29
+
30
+ def warn(s, verbose_only: bool = False):
31
+ """
32
+ Print a warning message to stderr.
33
+
34
+ Args:
35
+ s: Warning message
36
+ verbose_only: If True, only print when verbose mode is enabled (but still logged to file)
37
+ """
38
+ # Get the logger for proper structured logging (name is consumer-configurable)
39
+ logger = logging.getLogger(_warn_logger_name)
40
+
41
+ if verbose_only:
42
+ # Log at INFO level: will appear in log file but not console in non-verbose mode
43
+ # (console handler is set to WARNING level in non-verbose mode)
44
+ logger.info(s)
45
+ else:
46
+ # Regular warning: always print to stderr
47
+ logger.warning(s)
48
+ print('Warning: %s, continuing' % s, file=sys.stderr)
49
+
50
+ def arrayify(d):
51
+ '''
52
+ Make an array from a dict with contiguous string-numeric keys.
53
+ '''
54
+ return [ d[i] for i in sorted(d.keys(), key=int) ]
55
+
56
+ def indices(d):
57
+ '''
58
+ Return properly ordered array indices for pseudo-array dict.
59
+ '''
60
+ return [ i for i in sorted(d.keys(), key=int) ]
61
+
62
+ def format_timestamp() -> str:
63
+ '''
64
+ Generate timestamp format YYYYMMDDHHMMSS for use in filenames.
65
+ '''
66
+ return datetime.now().strftime("%Y%m%d%H%M%S")
67
+
68
+
69
+ class BrokenPipeHandler(logging.StreamHandler):
70
+ """
71
+ Custom logging handler that gracefully handles broken pipe errors.
72
+
73
+ This is needed when piping output to commands like 'head' that may
74
+ close the pipe early, causing BrokenPipeError exceptions.
75
+ """
76
+ def emit(self, record):
77
+ try:
78
+ super().emit(record)
79
+ except BrokenPipeError:
80
+ # Ignore broken pipe errors (e.g., when piping to head)
81
+ pass
82
+ except Exception:
83
+ # Silently ignore other exceptions to prevent recursion
84
+ pass
85
+
86
+
87
+ def setup_logger(name: str, log_file: Optional[Path] = None, log_level: int = logging.INFO) -> logging.Logger:
88
+ """
89
+ Set up a logger with consistent formatting and broken pipe handling.
90
+
91
+ Args:
92
+ name: Logger name
93
+ log_file: Optional file path for logging (if None, only console logging)
94
+ log_level: Console logging level (file always captures everything)
95
+
96
+ Returns:
97
+ Configured logger instance
98
+ """
99
+ logger = logging.getLogger(name)
100
+ logger.setLevel(logging.DEBUG) # Logger itself should allow all levels
101
+
102
+ # Clear any existing handlers
103
+ logger.handlers.clear()
104
+
105
+ # Shared formatter for both console and file output
106
+ log_format = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s', datefmt='%Y%m%d %H:%M:%S')
107
+
108
+ # Console handler with broken pipe handling - respects user verbosity
109
+ console_handler = BrokenPipeHandler(sys.stdout)
110
+ console_handler.setLevel(log_level) # Console respects user's verbosity setting
111
+ console_handler.setFormatter(log_format)
112
+ logger.addHandler(console_handler)
113
+
114
+ # File handler if log file specified
115
+ if log_file:
116
+ file_handler = logging.FileHandler(log_file, mode='a')
117
+ file_handler.setLevel(logging.DEBUG) # File should capture everything
118
+ file_handler.setFormatter(log_format)
119
+ logger.addHandler(file_handler)
120
+
121
+ return logger
122
+
123
+ def relativize_path(path: str, output_dir: Path) -> str:
124
+ """
125
+ Convert absolute paths to relative paths based on output directory.
126
+
127
+ Args:
128
+ path: File path (absolute or relative)
129
+ output_dir: Output directory for relativization
130
+
131
+ Returns:
132
+ Path relative to output directory, or original path if conversion fails
133
+ """
134
+ try:
135
+ path_obj = Path(path)
136
+ if path_obj.is_absolute():
137
+ # Convert to relative path from output directory
138
+ relative_path = path_obj.relative_to(output_dir)
139
+ return str(relative_path)
140
+ else:
141
+ # Already relative, return as-is
142
+ return path
143
+ except (ValueError, OSError):
144
+ # If relative_to fails (path not under output_dir), return original
145
+ return path
146
+
@@ -0,0 +1,120 @@
1
+ Metadata-Version: 2.4
2
+ Name: solver-support
3
+ Version: 0.1.1
4
+ Summary: Generic Conjure / Savile Row / Fast Downward / SymK / ENHSP / UPF solver pipeline
5
+ Author: András Salamon
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/ott2/solver-support
8
+ Project-URL: Repository, https://github.com/ott2/solver-support
9
+ Project-URL: Issues, https://github.com/ott2/solver-support/issues
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: psutil>=5.8.0
26
+ Requires-Dist: PyYAML>=5.4
27
+ Dynamic: license-file
28
+
29
+ # solver-support
30
+
31
+ A generic, domain-agnostic pipeline for running constraint-modelling and
32
+ planning solvers: **Conjure** / **Savile Row** (+ SAT/CP backends), **Fast
33
+ Downward**, **SymK**, **ENHSP**, and **UPF**-constructed models. It handles the
34
+ mechanics that every such run needs — building solver command lines from a model
35
+ registry, spawning subprocesses with timeouts and clean teardown, naming the
36
+ per-run files, parsing solver output into a uniform timing / cost-layer model,
37
+ and driving the horizon (increasing-difficulty) loop.
38
+
39
+ It carries **no** notion of any particular problem domain or objective. A
40
+ consumer supplies:
41
+
42
+ - its own **model registry** (a `models.yaml`) via a config path — the loader is
43
+ generic, the model content is the consumer's;
44
+ - its **scoring / objective strategy** via injection — the core reports the raw
45
+ objective a solver emits and lets the consumer interpret it (is-this-an-
46
+ optimisation-model, how to read the objective, any reporting multiplier).
47
+
48
+ ## Status
49
+
50
+ **Alpha.** The pipeline is in day-to-day use by two consumers and is covered by
51
+ its own test suite, but this is the first public release and the API may still
52
+ shift between 0.x versions.
53
+
54
+ The package ships **unwired**: a bare `import solver_support` gives a pipeline
55
+ that resolves no model registry and interprets no objective, because it has no
56
+ way to guess either. A consumer registers its scoring, registry paths, warning
57
+ logger and UPF CLI once, before first use.
58
+
59
+ ## Installation
60
+
61
+ ```bash
62
+ pip install solver-support
63
+ ```
64
+
65
+ Requires Python 3.9 or newer (3.9.6 is the system Python on macOS, and is
66
+ supported deliberately). The only runtime dependencies are `psutil` and `PyYAML`.
67
+
68
+ ## Usage
69
+
70
+ See [`docs/injection-api.md`](https://github.com/ott2/solver-support/blob/main/docs/injection-api.md)
71
+ for the four injection points a consumer wires — model-registry paths, objective
72
+ extraction, warning logger, UPF CLI — with a worked example.
73
+
74
+ ## Docs
75
+
76
+ - [`injection-api.md`](https://github.com/ott2/solver-support/blob/main/docs/injection-api.md)
77
+ — the four seams a consumer wires.
78
+ - [`solver-runner.md`](https://github.com/ott2/solver-support/blob/main/docs/solver-runner.md)
79
+ — running a solver as a subprocess: `SolverResult`, timeout vs duration, phases.
80
+ - [`timing-model.md`](https://github.com/ott2/solver-support/blob/main/docs/timing-model.md)
81
+ — how solver timing is recorded, how the backends map onto one shape, and the
82
+ invariants parsers rely on.
83
+
84
+ ## Development
85
+
86
+ Clone and install editable. A consumer under development alongside it is
87
+ normally installed into the same virtualenv, so both are importable and edits to
88
+ either take effect immediately:
89
+
90
+ ```bash
91
+ git clone https://github.com/ott2/solver-support
92
+ pip install -e solver-support
93
+ ```
94
+
95
+ ## Tests
96
+
97
+ `pytest tests/` — the core's own suite, and the home for solver-level
98
+ development. It runs against `solver_support` alone: no consumer need be
99
+ installed, and none is imported.
100
+
101
+ Since the core ships unwired, a test that needs a seam wired wires a stand-in
102
+ (`tests/conftest.py`):
103
+
104
+ | fixture | supplies |
105
+ |---------|----------|
106
+ | `fixture_registry` | a `ModelRegistry` over `tests/fixtures/models.yaml`, this suite's own synthetic config — never a consumer's |
107
+ | `wired_stdout_score` | a consumer-style `Score: N` extractor, so a score reaches `solver_stats` |
108
+ | `wired_timeout_score` | a consumer-style extractor for a salvaged timeout solution |
109
+
110
+ Wiring a fixture config rather than mocking the registry keeps the real loader
111
+ and accessors in the path. Anything a test needs from the registry goes in
112
+ `tests/fixtures/models.yaml`, keeping its shape faithful to a real consumer
113
+ config — that shape is the contract the loader implements.
114
+
115
+ ## Licence
116
+
117
+ BSD-3-Clause — see
118
+ [`LICENSE`](https://github.com/ott2/solver-support/blob/main/LICENSE).
119
+
120
+ By András Salamon, with Claude Opus 4.8 and 5.
@@ -0,0 +1,21 @@
1
+ solver_support/__init__.py,sha256=b227_QtUysX4TjTJ3479hJ7TxtPb0XOJJ3MFAawHgh8,3824
2
+ solver_support/command_builders.py,sha256=i9inzQg8O_bQdg67Phx3fSX6mPIDZG8stH6nzqoRLBI,15920
3
+ solver_support/file_management.py,sha256=3rMyl7kSVFDTSbbz3plx_ZLJHxAtt3nDYcJMK8GwF_c,8756
4
+ solver_support/global_state.py,sha256=yf2v6SeXKlSXp6Sr3cTY2SQILJaBH9wiMOTOZbnOQNI,2132
5
+ solver_support/horizon_utils.py,sha256=FvdLQmUmfWOcy07KRgkmdmSusjtEmLcCkMyikKBk2JM,16070
6
+ solver_support/model_registry.py,sha256=DeHofjm5YgLHaNrE_98kx4uj3e0Jw4KUIXjZ81x6ms4,31382
7
+ solver_support/objective.py,sha256=BciFu6AJNhMakXesQ4DXE7k2w1O6xB6ISHGogM5GCVk,4161
8
+ solver_support/run_summary.py,sha256=IV_tY8j49gEsJsTQ8e-S5gaLIRJ5ssUQ0Tk-wYY6pW0,16566
9
+ solver_support/solver_base.py,sha256=-9agNTxOYzSuhedodmZ7-A6j1SibahCFLbGrGsfKam8,4878
10
+ solver_support/solver_enhsp.py,sha256=3EyU0xtkyqpSDdEyNDeqpJMomfBds9QceiR1FLyKEZI,4289
11
+ solver_support/solver_fd.py,sha256=8I0cmCB-p0q63yokX01D527lLIBtVi-_Fo-E2PxXCpk,12325
12
+ solver_support/solver_horizon.py,sha256=-ob2Q6ifhN3aknZWm-SqgquivIa_ZYkK7aPoMEeFCAw,30538
13
+ solver_support/solver_runner.py,sha256=yFw35qZGyvjfBlC6og2Wno7eN8zRkVKv1stSoiCVPeg,30363
14
+ solver_support/solver_stats.py,sha256=kIOeebbicXcVQooJgjMMS0PHtB1LTFTUbJqSn0lDpkU,34910
15
+ solver_support/solver_upfplan.py,sha256=F4y1urb1KHAjNN3OcrqfKfMAXW646rg7EtVROgFvU6M,8250
16
+ solver_support/utilities.py,sha256=Aan8L-OTSzEpNf8-krrP1cfh5FMUmYDGkKn4KRJQfqM,4920
17
+ solver_support-0.1.1.dist-info/licenses/LICENSE,sha256=xaDouhdJlWIrxBMOcomqpCBqJsc4MgHFknB4l9yT6QM,1523
18
+ solver_support-0.1.1.dist-info/METADATA,sha256=_2Ffip1nOU9l_X58tjkl86QrAzayb3Y4strQOTU2DsE,5053
19
+ solver_support-0.1.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
20
+ solver_support-0.1.1.dist-info/top_level.txt,sha256=GmfATXT2pguSk0_6_J-3chal_ql5vxBmXGacUWTBe5w,15
21
+ solver_support-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, András Salamon
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1 @@
1
+ solver_support