diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7a57db4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: Continuous Integration + +on: + pull_request: + branches: [ main, develop ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Compose + run: | + sudo apt-get update + sudo apt-get install -y docker-compose + + - name: Build and start services + run: docker-compose up --build -d + + - name: Run Tests + run: docker-compose exec -T gateway test + + - name: Shutdown services + if: always() + run: docker-compose down diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c5f3f6b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "interactive" +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..52bd145 --- /dev/null +++ b/LICENSE @@ -0,0 +1,395 @@ +Attribution 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. \ No newline at end of file diff --git a/README.md b/README.md index 2ea26c6..2223c01 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,184 @@ # OpenBinding -OpenBinding is a QoS-aware service composition gateway and solver engine. +OpenBinding is a QoS-aware service composition gateway and solver engine framework. It provides a unified interface to model and solve service composition problems using various underlying optimization engines. -## Structure +## 🏗️ Architecture -- `openbinding-gateway/`: Python FastAPI gateway services. -- `engines/minizinc-csp/`: TypeScript/Node.js MiniZinc CSP solver engine. -- `schemas/`: JSON Schemas for problem instances and engine specializations. -- `examples/`: Example JSON payloads. +```mermaid +graph TD + User[User / Frontend] -->|HTTP POST /v1/solve| Gateway[OpenBinding Gateway] + Gateway -->|1. Validate General Schema| Schema[General QoS Schema] + Gateway -->|2. Validate Specialization| Spec[Specialization Schema] + Gateway -->|3. Route Request| Router{Router} + + Router -->|engine_id=minizinc-csp| MZ[MiniZinc CSP Engine] + Router -->|engine_id=random-search| RS[Random Search Engine] + + MZ -->|Solve via Gecode| Solution + RS -->|Solve via Evolution| Solution + + Solution --> Gateway + Gateway -->|HTTP 200| User +``` + +## 🧩 Components + +1. **OpenBinding Gateway** (`openbinding-gateway`): + * Python FastAPI service acting as the central entry point. + * Handles schema validation (General & Specialization). + * Routes requests to appropriate engines. + * Provides analysis and diagnostics tools. + +2. **MiniZinc CSP Engine** (`engines/minizinc-csp`): + * TypeScript/Node.js service. + * Transforms problems into MiniZinc models. + * Solves using the Gecode constraint solver. + * Best for exact solutions to smaller/medium problems. + +3. **Random Search Engine** (`engines/random-search`): + * Java service. + * Uses random search. + * Best for exploring large solution spaces. -## usage +4. **Frontend** (`frontend`): + * React + Vite web UI for modeling and submitting problems. + * Multi-page SPA with professional design inspired by modern developer tools. + * **Features**: + - **Home**: Landing page showcasing OpenBinding features and engines + - **Playground**: Interactive workspace with JSON editor, engine selector, and result visualization + - **Engines Explorer**: Browse and compare solver engines with capabilities + - **Schema Explorer**: Interactive JSON schema viewer with search and navigation + - **Light/Dark Theme**: System-aware theme with persistence + +## 🚀 Getting Started ### Prerequisites -- Docker & Docker Compose +* **Docker** and **Docker Compose** +* (Optional) Python 3.11+ for local development + +### Installation & Running + +1. **Start the Stack**: + ```bash + docker compose up --build + ``` + + The services will be available at: + * **Frontend**: [http://localhost:80](http://localhost:80) + * **Gateway API**: [http://localhost:8000/docs](http://localhost:8000/docs) + * **MiniZinc Engine**: Port 3000 (Internal) + * **Random Search Engine**: Port 8081 (Internal) + +2. **Stop the Stack**: + ```bash + docker compose down + ``` + +### 💻 Local Development (No Docker) + +If you have the necessary runtimes installed (Python 3.11+, Node.js 20.19+, Java 17+, and Maven), you can run the components locally for faster development: + +1. **Gateway** (Python): + ```bash + cd openbinding-gateway + # Install dependencies with 'test' extras + uv pip install -e ".[test]" + # Run the gateway + uvicorn openbinding_gateway.main:app --host 0.0.0.0 --port 8000 + ``` + +2. **Frontend** (React + Vite): + ```bash + cd frontend + # Install dependencies (requires Node.js 20.19+ or 22.12+) + pnpm install + # Set API URL (optional, defaults to http://localhost:8000) + echo "VITE_API_BASE_URL=http://localhost:8000" > .env + # Run development server + pnpm run dev -- --host 0.0.0.0 --port 80 + ``` + The frontend will be available at [http://localhost:5173](http://localhost:5173) + +3. **MiniZinc CSP Engine** (Node.js + MiniZinc): + - Requirements: [MiniZinc](https://www.minizinc.org/) installed and in system PATH. + ```bash + cd engines/minizinc-csp + npm install + npm run dev + ``` + +4. **Random Search Engine** (Java + Maven): + ```bash + cd engines/random-search + # Build and run + mvn compile exec:java -Dexec.mainClass="es.us.isa.qosawarewsbinding.Controller" + ``` + +--- + +## 🛠️ Validation & Testing -### Running +OpenBinding implements a rigorous multi-stage validation process: +1. **General Schema**: Ensures the input adheres to the simplified QoS specification structure. +2. **Specialization Schema**: Enforces engine-specific constraints (e.g., supported composition types, constraints). +3. **Semantic/Logic**: Checks for consistency (e.g., undefined tasks, valid IDs). +4. **Analysis**: Computes binding space cardinality and generates warnings for potential issues. + +### Enhanced Validation Responses + +The gateway now returns structured validation errors and warnings: + +- **`/v1/analyze`**: Returns detailed warnings with `code`, `message`, and `details` (including `path`, `constraint_id`, `stage`) +- **`/v1/solve`**: Returns HTTP 422 on validation failure with structured violations in the same format + +### Running Tests (Docker) + +To run the complete test suite in the Docker environment: ```bash -docker compose up --build +# 1. Ensure stack is running +docker compose up -d + +# 2. Run all tests +docker compose exec gateway test + +# 3. Run specific test file +docker compose exec gateway test tests/test_analysis.py -v ``` -### API +### Running Tests (Local) -- **Solve**: `POST /v1/solve` +If running **locally** without Docker: ```bash -curl -X POST -H "Content-Type: application/json" -d @examples/minimal-1task.json http://localhost:8000/v1/solve -``` \ No newline at end of file +cd openbinding-gateway +pytest +# Or run with verbose output +pytest -v +# Or run specific tests +pytest tests/test_validation_comprehensive.py -v +``` + +**Test Coverage**: 60 tests covering validation, analysis, routing, and integration with engines. + +## 📝 Usage Example + +Submit a problem to the MiniZinc engine: + +```bash +curl -X POST "http://localhost:8000/v1/solve" \ + -H "Content-Type: application/json" \ + -d '{ + "engine_id": "minizinc-csp", + "verbose": true, + "instance": { ... JSON content ... } + }' +``` + +See `examples/` directory for sample payloads. + +## 📄 License + +This project is licensed under the **Creative Commons Attribution 4.0 International (CC BY 4.0)**. +See the [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index e019a74..39a1149 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3.8' - services: gateway: build: @@ -9,11 +7,25 @@ services: - "8000:8000" volumes: - ./schemas:/app/schemas + - ./openbinding-gateway:/app + - ./examples/generated_instances:/app/examples/generated_instances environment: - - UNIVERSAL_SCHEMA_PATH=/app/schemas/universal/schema.json + - GENERAL_SCHEMA_PATH=/app/schemas/general/schema.json - SCHEMAS_DIR=/app/schemas + - PYTHONPATH=/app/src + - ENGINE_MINIZINC_URL=http://engine-minizinc:3000 + - ENGINE_RANDOM_SEARCH_URL=http://engine-random-search:8080 + healthcheck: + test: ["CMD", "curl", "-fsS", "http://localhost:8000/health"] + interval: 10s + timeout: 3s + retries: 5 + start_period: 10s depends_on: - - engine-minizinc + engine-minizinc: + condition: service_healthy + engine-random-search: + condition: service_healthy engine-minizinc: build: @@ -21,12 +33,34 @@ services: dockerfile: Dockerfile ports: - "3000:3000" + healthcheck: + test: ["CMD", "curl", "-fsS", "http://localhost:3000/health"] + interval: 10s + timeout: 3s + retries: 5 + start_period: 20s + + engine-random-search: + build: + context: ./engines/random-search + dockerfile: Dockerfile + ports: + - "8081:8080" + healthcheck: + test: ["CMD", "curl", "-fsS", "http://localhost:8080/health"] + interval: 10s + timeout: 3s + retries: 5 + start_period: 30s frontend: build: context: ./frontend dockerfile: Dockerfile ports: - - "8080:80" + - "80:80" + volumes: + - ./examples:/usr/share/nginx/html/examples:ro depends_on: - - gateway + gateway: + condition: service_healthy diff --git a/engines/minizinc-csp/Dockerfile b/engines/minizinc-csp/Dockerfile index a49a929..2c7f052 100644 --- a/engines/minizinc-csp/Dockerfile +++ b/engines/minizinc-csp/Dockerfile @@ -5,8 +5,8 @@ ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable -# Install MiniZinc -RUN apt-get update && apt-get install -y minizinc && rm -rf /var/lib/apt/lists/* +# Install MiniZinc + healthcheck tooling +RUN apt-get update && apt-get install -y --no-install-recommends minizinc curl && rm -rf /var/lib/apt/lists/* WORKDIR /app diff --git a/engines/minizinc-csp/model/composition.mzn b/engines/minizinc-csp/model/composition.mzn index 88a7660..1da6056 100644 --- a/engines/minizinc-csp/model/composition.mzn +++ b/engines/minizinc-csp/model/composition.mzn @@ -1,5 +1,4 @@ -% OpenBinding CSP Model -% Supports TASK, SEQ, AND_PAR, XOR +% OpenBinding CSP Model (Generic QoS & Loop Support) % --- INPUTS --- @@ -11,25 +10,22 @@ float: PROB_EPS; int: n_tasks; int: n_candidates; int: n_nodes; +int: n_qos; % Number of QoS features % 2. Candidates -% Improved: per-task candidate lists for stronger propagation int: max_cands_per_task; array[1..n_tasks] of int: n_task_cands; array[1..n_tasks, 1..max_cands_per_task] of int: task_cands; -% QoS values (Cost, Time, Reliability, Availability, Security) -array[1..n_candidates] of float: c_cost; -array[1..n_candidates] of float: c_time; -array[1..n_candidates] of float: c_rel; -array[1..n_candidates] of float: c_avail; -array[1..n_candidates] of float: c_sec; +% Generic QoS values [candidate, feature_idx] +array[1..n_candidates, 1..n_qos] of float: cand_qos; % 3. Composition Structure (Flattened Tree) int: KIND_TASK = 1; int: KIND_SEQ = 2; int: KIND_AND = 3; int: KIND_XOR = 4; +int: KIND_LOOP = 5; array[1..n_nodes] of int: node_kind; array[1..n_nodes] of int: node_task_id; % 0 if not TASK @@ -37,24 +33,28 @@ int: max_children; array[1..n_nodes, 1..max_children] of int: node_children; array[1..n_nodes] of int: node_n_children; array[1..n_nodes, 1..max_children] of float: node_xor_probs; +array[1..n_nodes] of float: node_loop_iters; % Expected iterations for LOOP nodes -% 4. Objective Weights and Bounds for Scaling -float: w_cost; -float: w_time; -float: w_rel; -float: w_avail; -float: w_sec; +% 4. Aggregation Policies +% 1=SUM, 2=PROD, 3=MAX, 4=MIN, 5=WEIGHTED_SUM (for XOR) +int: AGG_SUM = 1; +int: AGG_PROD = 2; +int: AGG_MAX = 3; +int: AGG_MIN = 4; +int: AGG_WSUM = 5; -float: COST_UB; -float: TIME_UB; +% Policy map: [feature_idx, node_kind] -> function enum +array[1..n_qos, 1..5] of int: agg_policy; + +% 5. Objective Weights and Bounds +array[1..n_qos] of float: qos_weights; +array[1..n_qos] of float: qos_ub; % --- VARIABLES --- -% Decision: For each task, select an INDEX into its candidate list (not the global candidate ID directly) array[1..n_tasks] of var 1..max_cands_per_task: pick_idx; array[1..n_tasks] of var 1..n_candidates: selected_cand; -% Constraint: valid pick index constraint forall(t in 1..n_tasks)( pick_idx[t] <= n_task_cands[t] /\ selected_cand[t] = task_cands[t, pick_idx[t]] @@ -67,14 +67,14 @@ array[1..n_candidates] of int: candidate_provider; % 1. Global Constraints int: n_global_constraints; -array[1..n_global_constraints] of int: gc_attr; +array[1..n_global_constraints] of int: gc_attr; % Index into 1..n_qos array[1..n_global_constraints] of int: gc_op; array[1..n_global_constraints] of float: gc_val; % 2. Local Constraints int: n_local_constraints; array[1..n_local_constraints] of int: lc_task; -array[1..n_local_constraints] of int: lc_attr; +array[1..n_local_constraints] of int: lc_attr; % Index into 1..n_qos array[1..n_local_constraints] of int: lc_op; array[1..n_local_constraints] of float: lc_val; @@ -84,42 +84,109 @@ array[1..n_dep_constraints] of int: dc_type; array[1..n_dep_constraints] of int: dc_t1; array[1..n_dep_constraints] of int: dc_t2; -% Helper Predicate: Check Operation (Robust) +% Helper Predicate: Check Operation predicate check_op(var float: val, int: op, float: target) = if op == 1 then val <= target elseif op == 2 then val >= target - elseif op == 3 then (val <= target + 1e-4 /\ val >= target - 1e-4) % Range check + elseif op == 3 then (val <= target + 1e-4 /\ val >= target - 1e-4) elseif op == 4 then val < target elseif op == 5 then val > target else true endif; -% Enforce Global Constraints (on Root Node) +% --- AGGREGATION COMPUTATION --- + +% Computed QoS for each Node [node, feature] +array[1..n_nodes, 1..n_qos] of var float: node_qos; + +% Bound Constraint (Optional but helps solver) +constraint forall(i in 1..n_nodes, q in 1..n_qos)( + node_qos[i,q] >= 0.0 /\ node_qos[i,q] <= qos_ub[q] +); + +constraint forall(i in 1..n_nodes)( + if node_kind[i] == KIND_TASK then + let { var int: c = selected_cand[node_task_id[i]] } in + forall(q in 1..n_qos)( + node_qos[i,q] == cand_qos[c, q] + ) + elseif node_kind[i] == KIND_LOOP then + % Loop Aggregation + % Child is node_children[i,1] + let { + int: body_idx = node_children[i,1]; + float: iters = node_loop_iters[i]; + } in + % Loop Aggregation restored + forall(q in 1..n_qos)( + let { + int: pol = agg_policy[q, KIND_LOOP]; + var float: body_val = node_qos[body_idx, q]; + } in + if pol == AGG_SUM then node_qos[i,q] == body_val * iters + elseif pol == AGG_PROD then + % Product over iterations: val * val ... * val (iters times) -> pow + node_qos[i,q] == pow(body_val, iters) + elseif pol == AGG_MAX then node_qos[i,q] == body_val + elseif pol == AGG_MIN then node_qos[i,q] == body_val + else node_qos[i,q] == body_val * iters + endif + ) + else + % SEQ, AND, XOR - list children + forall(q in 1..n_qos)( + let { + int: pol = agg_policy[q, node_kind[i]]; + % Collect children values + % Use array comprehension + array[int] of var float: child_vals = [node_qos[node_children[i,j], q] | j in 1..node_n_children[i]]; + } in + if node_kind[i] == KIND_XOR then + % XOR special handling for Weighted Sum (Probabilities) + % Usually XOR aggregates using Weighted Sum of probabilities regardless of 'pol' if it's 'average' logic. + % But strictly, if pol=MAX, it implies MAX of branches. + % If pol=SUM, implies SUM of branches? (Seems wrong for XOR which runs only one). + % Usually XOR uses Weighted Sum (Expectation). + % If pol == AGG_WSUM (5) -> Weighted Sum + if pol == AGG_WSUM then + node_qos[i,q] == sum(j in 1..node_n_children[i])(node_xor_probs[i,j] * child_vals[j]) + elseif pol == AGG_MAX then + node_qos[i,q] == max(child_vals) + elseif pol == AGG_MIN then + node_qos[i,q] == min(child_vals) + else + % Default to weighted sum for XOR + node_qos[i,q] == sum(j in 1..node_n_children[i])(node_xor_probs[i,j] * child_vals[j]) + endif + else + % SEQ or AND + if pol == AGG_SUM then node_qos[i,q] == sum(child_vals) + elseif pol == AGG_PROD then node_qos[i,q] == product(child_vals) + elseif pol == AGG_MAX then node_qos[i,q] == max(child_vals) + elseif pol == AGG_MIN then node_qos[i,q] == min(child_vals) + else node_qos[i,q] == sum(child_vals) + endif + endif + ) + endif +); + +% --- CONSTRAINTS ENFORCEMENT --- + +% Global constraint forall(i in 1..n_global_constraints)( - let { var float: val = - if gc_attr[i] == 1 then n_cost[root_id] - elseif gc_attr[i] == 2 then n_time[root_id] - elseif gc_attr[i] == 3 then n_rel[root_id] - elseif gc_attr[i] == 4 then n_avail[root_id] - elseif gc_attr[i] == 5 then n_sec[root_id] - else 0.0 endif - } in check_op(val, gc_op[i], gc_val[i]) + let { var float: val = node_qos[root_id, gc_attr[i]] } + in check_op(val, gc_op[i], gc_val[i]) ); -% Enforce Local Constraints +% Local constraint forall(i in 1..n_local_constraints)( let { var int: c = selected_cand[lc_task[i]]; - var float: val = - if lc_attr[i] == 1 then c_cost[c] - elseif lc_attr[i] == 2 then c_time[c] - elseif lc_attr[i] == 3 then c_rel[c] - elseif lc_attr[i] == 4 then c_avail[c] - elseif lc_attr[i] == 5 then c_sec[c] - else 0.0 endif + var float: val = cand_qos[c, lc_attr[i]]; } in check_op(val, lc_op[i], lc_val[i]) ); -% Enforce Dependency Constraints +% Dependency constraint forall(i in 1..n_dep_constraints)( let { var int: c1 = selected_cand[dc_t1[i]]; @@ -133,87 +200,51 @@ constraint forall(i in 1..n_dep_constraints)( ); % --- STRUCTURAL SAFEGUARDS --- +% (Same as before, + Loop check) -% Child indices valid constraint forall(i in 1..n_nodes, j in 1..node_n_children[i])( node_children[i,j] >= 1 /\ node_children[i,j] <= n_nodes ); -% Non-leaf nodes have at least 1 child constraint forall(i in 1..n_nodes where node_kind[i] != KIND_TASK)( node_n_children[i] >= 1 ); -% Task nodes map to valid task id constraint forall(i in 1..n_nodes where node_kind[i] == KIND_TASK)( node_task_id[i] >= 1 /\ node_task_id[i] <= n_tasks ); -% Non-task nodes should have task_id = 0 constraint forall(i in 1..n_nodes where node_kind[i] != KIND_TASK)( node_task_id[i] == 0 ); -% XOR Probability Consistency constraint forall(i in 1..n_nodes where node_kind[i] == KIND_XOR)( forall(j in 1..node_n_children[i])(node_xor_probs[i,j] >= 0.0) /\ abs(sum(j in 1..node_n_children[i])(node_xor_probs[i,j]) - 1.0) <= PROB_EPS ); +% Loop specific: 1 child +constraint forall(i in 1..n_nodes where node_kind[i] == KIND_LOOP)( + node_n_children[i] == 1 +); -% --- AGGREGATION --- +% --- OBJECTIVE --- -% Computed QoS for each Node with Tight Bounds -array[1..n_nodes] of var 0.0..COST_UB: n_cost; -array[1..n_nodes] of var 0.0..TIME_UB: n_time; -array[1..n_nodes] of var 0.0..1.0: n_rel; -array[1..n_nodes] of var 0.0..1.0: n_avail; -array[1..n_nodes] of var 0.0..1.0: n_sec; -constraint forall(i in 1..n_nodes)( - if node_kind[i] == KIND_TASK then - let { var int: c = selected_cand[node_task_id[i]] } in ( - n_cost[i] == c_cost[c] /\ - n_time[i] == c_time[c] /\ - n_rel[i] == c_rel[c] /\ - n_avail[i] == c_avail[c] /\ - n_sec[i] == c_sec[c] - ) - elseif node_kind[i] == KIND_SEQ then - n_cost[i] == sum(j in 1..node_n_children[i])(n_cost[node_children[i,j]]) /\ - n_time[i] == sum(j in 1..node_n_children[i])(n_time[node_children[i,j]]) /\ - n_rel[i] == product(j in 1..node_n_children[i])(n_rel[node_children[i,j]]) /\ - n_avail[i] == product(j in 1..node_n_children[i])(n_avail[node_children[i,j]]) /\ - n_sec[i] == min(j in 1..node_n_children[i])(n_sec[node_children[i,j]]) - elseif node_kind[i] == KIND_AND then - n_cost[i] == sum(j in 1..node_n_children[i])(n_cost[node_children[i,j]]) /\ - n_time[i] == max(j in 1..node_n_children[i])(n_time[node_children[i,j]]) /\ - n_rel[i] == product(j in 1..node_n_children[i])(n_rel[node_children[i,j]]) /\ - n_avail[i] == product(j in 1..node_n_children[i])(n_avail[node_children[i,j]]) /\ - n_sec[i] == min(j in 1..node_n_children[i])(n_sec[node_children[i,j]]) - elseif node_kind[i] == KIND_XOR then - n_cost[i] == sum(j in 1..node_n_children[i])(node_xor_probs[i,j] * n_cost[node_children[i,j]]) /\ - n_time[i] == sum(j in 1..node_n_children[i])(node_xor_probs[i,j] * n_time[node_children[i,j]]) /\ - n_rel[i] == sum(j in 1..node_n_children[i])(node_xor_probs[i,j] * n_rel[node_children[i,j]]) /\ - n_avail[i] == sum(j in 1..node_n_children[i])(node_xor_probs[i,j] * n_avail[node_children[i,j]]) /\ - n_sec[i] == sum(j in 1..node_n_children[i])(node_xor_probs[i,j] * n_sec[node_children[i,j]]) - else - true - endif -); - -% Root node used for objective -var float: obj_val = - w_cost * n_cost[root_id] + - w_time * n_time[root_id] + - w_rel * (1.0 - n_rel[root_id]) + - w_avail * (1.0 - n_avail[root_id]) + - w_sec * (1.0 - n_sec[root_id]); +% Calculate explicit upper bound for objective to prevent Gecode overflow +float: obj_ub = sum(q in 1..n_qos)(abs(qos_weights[q]) * abs(qos_ub[q])); +var -obj_ub..obj_ub: obj_val = sum(q in 1..n_qos)(qos_weights[q] * node_qos[root_id, q]); solve minimize obj_val; output [ "{ \"objective_value\": " ++ show(obj_val) ++ ", ", + "\"aggregated_features\": {", + join(", ", [ + "\"" ++ show(q) ++ "\": " ++ show(node_qos[root_id, q]) + | q in 1..n_qos + ]), + "}, ", "\"selection\": {", join(", ", [ "\"" ++ show(t) ++ "\": " ++ show(selected_cand[t]) diff --git a/engines/minizinc-csp/src/index.ts b/engines/minizinc-csp/src/index.ts index 40fb017..6d089d1 100644 --- a/engines/minizinc-csp/src/index.ts +++ b/engines/minizinc-csp/src/index.ts @@ -6,6 +6,10 @@ const fastify = Fastify({ logger: true }); const solver = new Solver(); const jobManager = new JobManager(); +fastify.get('/health', async () => { + return { status: 'ok' }; +}); + fastify.post('/solve', async (request, reply) => { const body = request.body as any; const instance = body.instance; diff --git a/engines/minizinc-csp/src/solver.ts b/engines/minizinc-csp/src/solver.ts index 7331bc7..8b4c45d 100644 --- a/engines/minizinc-csp/src/solver.ts +++ b/engines/minizinc-csp/src/solver.ts @@ -1,14 +1,12 @@ import { spawn } from 'child_process'; -import * as fs from 'fs/promises'; import * as path from 'path'; +import * as fs from 'fs'; export class Solver { - private readonly SOLVER_NAME = 'gecode'; // TODO: Make this configurable. For now, only gecode is supported. + private readonly SOLVER_NAME = 'gecode'; + private readonly tmpDir = path.resolve(__dirname, '../tmp_minizinc'); async solve(instance: any, options: any): Promise { - const jobName = `job-${Date.now()}-${Math.random().toString(36).substring(7)}`; - const dznPath = path.join('/tmp', `${jobName}.dzn`); - // 0. Best Practices Validation const violations = this.validateBestPractices(instance); if (violations.length > 0) { @@ -22,19 +20,28 @@ export class Solver { }); } - // 1. Transform Instance to DZN - const dznContent = this.transformToDZN(instance, options); - await fs.writeFile(dznPath, dznContent); + // 1. Identify Features + const features = this.getFeatures(instance); + + // 2. Transform Instance to DZN + const dznContent = this.transformToDZN(instance, features, options); - // 2. Run MiniZinc - // minizinc --solver model.mzn data.dzn + // 3. Run MiniZinc + // minizinc --solver model.mzn - + // The '-' argument tells minizinc to read data from stdin const modelPath = path.resolve(__dirname, '../model/composition.mzn'); const startTime = Date.now(); return new Promise((resolve, reject) => { - // NOTE: Removed '--output-mode', 'json' to rely on the custom 'output' item in .mzn - // which includes 'objective_value' and 'selection'. - const minizinc = spawn('minizinc', ['--solver', this.SOLVER_NAME, modelPath, dznPath]); + const args = ['--solver', this.SOLVER_NAME, modelPath, '-']; + + // Ensure TMPDIR directory exists for MiniZinc + if (!fs.existsSync(this.tmpDir)) { + fs.mkdirSync(this.tmpDir, { recursive: true }); + } + const env = { ...process.env, TMPDIR: this.tmpDir }; + + const minizinc = spawn('minizinc', args, { env }); let stdout = ''; let stderr = ''; @@ -42,6 +49,10 @@ export class Solver { minizinc.stdout.on('data', (data) => stdout += data.toString()); minizinc.stderr.on('data', (data) => stderr += data.toString()); + // Write DZN content to stdin + minizinc.stdin.write(dznContent); + minizinc.stdin.end(); + minizinc.on('close', async (code) => { const endTime = Date.now(); const timeSec = (endTime - startTime) / 1000; @@ -50,8 +61,6 @@ export class Solver { if (stdout) console.log(`MiniZinc Stdout: ${stdout}`); if (stderr) console.error(`MiniZinc Stderr: ${stderr}`); - await fs.unlink(dznPath).catch(() => { }); // Cleanup - if (code !== 0) { return resolve({ solution: { @@ -80,9 +89,6 @@ export class Solver { } // MiniZinc Output Handling - // We expect a valid JSON string from the 'output' item in the .mzn file. - // However, MiniZinc might print "----------" or other delimiters. - // We look for the JSON block { ... } const lastBrace = stdout.lastIndexOf('}'); const firstBrace = stdout.indexOf('{'); if (firstBrace === -1 || lastBrace === -1) { @@ -105,16 +111,12 @@ export class Solver { const result = JSON.parse(jsonStr); // Map selected candidates back to IDs - // Result.selection is { "1": 5, "2": 3 } (task_idx -> cand_idx) - // We need to map back to task_id -> cand_id - const taskMap = this.mapTasks(instance); // task_idx -> task_id const candMap = this.mapCandidates(instance); // cand_idx -> cand_id const selection: Record = {}; if (Array.isArray(result.selected_cand)) { - // Fallback if MZN output format changes back to array result.selected_cand.forEach((cIdx: number, idx: number) => { const taskIdx = idx + 1; const tId = taskMap[taskIdx]; @@ -124,9 +126,7 @@ export class Solver { } }); } else if (result.selection) { - // Custom output from .mzn for (const [tIdx, cIdx] of Object.entries(result.selection)) { - // key is string "1", val is number/string const tId = taskMap[parseInt(tIdx)]; const cId = candMap[String(cIdx)]; if (tId && cId) { @@ -135,11 +135,32 @@ export class Solver { } } + // Map Aggregated Features: Indices -> Feature IDs + const aggregated_features: Record = {}; + if (result.aggregated_features) { + for (const [idxStr, val] of Object.entries(result.aggregated_features)) { + const fIdx = parseInt(idxStr); + // featureMap reverses: name -> idx. We need idx -> name + // features array is sorted: 0-based. fIdx is 1-based. + const featName = features[fIdx - 1]; + if (featName) { + aggregated_features[featName] = Number(val); + } + } + } + + // Handle Normalized Objective Scaling + // If objective was normalized, the result might be scaled. + // But actually minizinc result is just the sum. + // If using normalized weights, the result is in normalized range (0-1 approx). + // We just pass it through. + resolve({ solution: { selection: selection, objective_value: result.objective_value !== undefined ? result.objective_value : 0, - feasible: true + feasible: true, + aggregated_features: aggregated_features }, provenance: { solver: this.SOLVER_NAME, @@ -160,134 +181,209 @@ export class Solver { }); } - private transformToDZN(instance: any, options: any): string { + private getFeatures(instance: any): string[] { + // 1. Collect all declared features from instance.features or fallback to keys in agg policies or candidates + const declaredFeatures = (instance.features || []).map((f: any) => f.id); + const featureSet = new Set(declaredFeatures); + + // Also scan policies + if (instance.aggregation_policies) { + Object.keys(instance.aggregation_policies).forEach(k => featureSet.add(k)); + } + + // Scan candidates for any extra keys? (Optional, maybe stick to declared to avoid garbage) + // Let's stick to featureSet. If empty, scan one candidate. + if (featureSet.size === 0 && instance.candidates && instance.candidates.length > 0) { + const c = instance.candidates[0]; + const qosData = c.qos || c.features || {}; + Object.keys(qosData).forEach(k => featureSet.add(k)); + } + + return Array.from(featureSet).sort(); // Consistent order + } + + private transformToDZN(instance: any, features: string[], options: any): string { // Helper to format arrays const fmt = (arr: any[]) => `[${arr.join(', ')}]`; - const fmt2d = (arr: any[][]) => `[| ${arr.map(r => r.join(', ')).join(' | ')} |]`; + const fmt2d = (arr: any[][]) => { + if (arr.length === 0) return `[| |]`; + // MiniZinc 2D array syntax: [| r1 | r2 | ... |] + return `[| ${arr.map(r => r.join(', ')).join(' | ')} |]`; + }; const tasks = instance.tasks || []; const candidates = instance.candidates || []; const n_tasks = tasks.length; const n_candidates = candidates.length; - // Identify task indices (1-based) + // --- 0. Identify QoS Features & Policies --- + // Features passed as argument + const n_qos = features.length; + const featureMap: Record = {}; + features.forEach((f, i) => featureMap[f] = i + 1); + + + + // Aggregation Enums: 1=SUM, 2=PROD, 3=MAX, 4=MIN, 5=WSUM + const FN_MAP: Record = { + "sum": 1, "weighted_sum": 5, "product": 2, "max": 3, "min": 4, + "scale_by_c": 1 // Loop specific: usually means sum * c + }; + const DEFAULT_FN = 1; // Sum + + // Build Aggregation Policy Matrix: [feature_idx, kind_idx] + // Kinds: TASK=1, SEQ=2, AND=3, XOR=4, LOOP=5 + const agg_policy: number[][] = []; + + for (const feat of features) { + const pol = (instance.aggregation_policies || {})[feat] || {}; + const compose = pol.compose || {}; + + const row: number[] = []; + // 1. TASK (N/A really, but filler) + row.push(DEFAULT_FN); + + // 2. SEQ + row.push(FN_MAP[compose.seq?.fn] || DEFAULT_FN); + + // 3. AND + row.push(FN_MAP[compose.and?.fn] || DEFAULT_FN); // Usually MAX or SUM + + // 4. XOR + // Default XOR to WSUM (5) unless specified + row.push(FN_MAP[compose.xor?.fn] || 5); + + // 5. LOOP + row.push(FN_MAP[compose.loop?.fn] || DEFAULT_FN); + + agg_policy.push(row); + } + + // --- 1. Map Tasks & Providers --- const taskIdx: Record = {}; tasks.forEach((t: any, i: number) => taskIdx[t.id] = i + 1); - // Identify provider indices (1-based) const providerIdx: Record = {}; if (instance.providers) { instance.providers.forEach((p: any, i: number) => providerIdx[p.id] = i + 1); } - // --- 1. Compute Per-Task Candidate Lists & Bounds --- - // We need: - // - max_cands_per_task - // - task_cands[t, c_idx] -> cand_index (global) - // - n_task_cands[t] - // - candidate_provider (global map) - - // Also Compute Bounds for Domains - // COST_UB: Sum over tasks of their max cost candidate - // TIME_UB: Sum over tasks of their max time candidate (Worst case SEQ) - + // --- 2. Candidates & QoS Matrix --- const task_candidates_map: number[][] = Array.from({ length: n_tasks + 1 }, () => []); - - const c_cost_arr: number[] = []; - const c_time_arr: number[] = []; - const c_rel_arr: number[] = []; - const c_avail_arr: number[] = []; - const c_sec_arr: number[] = []; const cand_provider: number[] = []; + const cand_qos: number[][] = []; // [cand][feat] - // Global candidate properties candidates.forEach((c: any, i: number) => { const global_idx = i + 1; const t_id = taskIdx[c.task_id]; - - // Populate per-task list - if (t_id) { - task_candidates_map[t_id].push(global_idx); - } + if (t_id) task_candidates_map[t_id].push(global_idx); cand_provider.push(providerIdx[c.provider_id] || 0); - const q = c.qos || {}; - c_cost_arr.push(q.cost || 0); - c_time_arr.push(q.time || 0); - c_rel_arr.push(q.reliability || 1.0); - c_avail_arr.push(q.availability || 1.0); - c_sec_arr.push(q.security || 1.0); + // QoS Values - support both 'qos' and 'features' property names + const qosData = c.qos || c.features || {}; + const row: number[] = []; + for (const feat of features) { + let val = qosData[feat]; + // Handle missing values? Default to 0? Or Worst case? + // For logic, 0.0 is safest default unless it's reliability (should be 1?) + // Trying to be smart: if 'product' agg, default to 1? + // For now, default 0.0. User should provide complete data. + if (val === undefined || val === null) val = 0.0; + row.push(Number(val)); + } + cand_qos.push(row); }); - // Compute Max Candidates Per Task for Matrix Size + // Max candidates per task let max_cands_per_task = 0; for (let t = 1; t <= n_tasks; t++) { if (task_candidates_map[t].length > max_cands_per_task) { max_cands_per_task = task_candidates_map[t].length; } } - if (max_cands_per_task === 0) max_cands_per_task = 1; // Avoid 0 size + if (max_cands_per_task === 0) max_cands_per_task = 1; - // Pad task_cands matrix - const task_cands: number[][] = []; // 1-based logic, 0-index in JS + const task_cands: number[][] = []; const n_task_cands: number[] = []; - // Compute UB - let COST_UB = 0.0; - let TIME_UB = 0.0; - for (let t = 1; t <= n_tasks; t++) { const cands = task_candidates_map[t]; n_task_cands.push(cands.length); - - // Pad const row = [...cands]; - while (row.length < max_cands_per_task) { - row.push(1); // Dummy filler, won't be picked if constraint holds - } + while (row.length < max_cands_per_task) row.push(1); // Pad task_cands.push(row); + } - // Bounds accumulation - if (cands.length > 0) { - const max_cost = Math.max(...cands.map(ci => c_cost_arr[ci - 1])); - const max_time = Math.max(...cands.map(ci => c_time_arr[ci - 1])); - COST_UB += max_cost; - TIME_UB += max_time; + // --- 3. Compute Bounds (QoS UB) --- + // Use tighter bounds to avoid Gecode float overflow + // For SUM aggregation: max possible is sum of max candidates per task + // We compute a reasonable upper bound based on actual data + const qos_ub: number[] = []; + for (let f = 0; f < n_qos; f++) { + // Check max value in candidates + let maxVal = 1.0; + if (candidates.length > 0 && cand_qos.length > 0) { + maxVal = Math.max(1.0, ...cand_qos.map(row => Math.abs(row[f]))); } - } - // Safety margin for float errors or structure overhead - if (COST_UB === 0) COST_UB = 1000000.0; - if (TIME_UB === 0) TIME_UB = 1000000.0; + // Check aggregation policy for SEQ/LOOP (columns 1 and 4 in 0-based row) + // Indices in MZN: SEQ=2, LOOP=5. In 0-based array: 1, 4. + const seqPol = agg_policy[f]?.[1] || 1; + const loopPol = agg_policy[f]?.[4] || 1; + + if (seqPol === 1 || loopPol === 1) { // SUM + // For SUM: upper bound = max_value * number_of_tasks * reasonable_loop_factor + // Use a tight bound to avoid Gecode overflow + const sumBound = maxVal * Math.max(n_tasks, 1) * 100; + qos_ub.push(sumBound); + } else if (seqPol === 2 || loopPol === 2) { // PROD + // If values > 1, grows fast. If <= 1, stays <= 1. + // Assume normalized 0-1 for reliability if product used. + if (maxVal <= 1.0) qos_ub.push(1.0); + else qos_ub.push(Math.pow(maxVal, Math.min(n_tasks, 10))); + } else { + // MAX/MIN - bound is just the max candidate value with margin + qos_ub.push(maxVal * 2); + } + } - // --- 2. Flatten Tree & Identify Root --- + // --- 4. Flatten Tree --- const nodes: any[] = []; - - // Recursive traversal const traverse = (node: any): number => { const myIdx = nodes.length + 1; + + // LOOP iterations: support both 'iterations' and 'expected_iterations' + let loopIters = 0.0; + if (node.kind === 'LOOP') { + loopIters = node.iterations || node.expected_iterations || 1.0; + } + const nodeEntry = { kind: this.getKind(node.kind), task_id: node.kind === 'TASK' ? taskIdx[node.task_id] : 0, children: [] as number[], - xor_probs: [] as number[] + xor_probs: [] as number[], + loop_iters: loopIters }; nodes.push(nodeEntry); if (node.children) { + // For XOR nodes, children may have 'probability' property + const isXOR = node.kind === 'XOR'; for (const child of node.children) { const childIdx = traverse(child); nodeEntry.children.push(childIdx); - nodeEntry.xor_probs.push(0.0); + // Extract probability for XOR children + nodeEntry.xor_probs.push(isXOR ? (child.probability || 0.0) : 0.0); } - } else if (node.branches) { // XOR + } else if (node.branches) { // XOR with branches format for (const br of node.branches) { const childIdx = traverse(br.child); nodeEntry.children.push(childIdx); - nodeEntry.xor_probs.push(br.p); + nodeEntry.xor_probs.push(br.p || 0.0); } - } else if (node.body) { // LOOP (Not fully supported, treat as SEQ child) + } else if (node.body) { // LOOP: body is single child const childIdx = traverse(node.body); nodeEntry.children.push(childIdx); nodeEntry.xor_probs.push(0.0); @@ -297,37 +393,29 @@ export class Solver { const root = instance.composition.root; let root_id = 1; - if (root) { - root_id = traverse(root); // Should be 1 because we traverse recursively pushing to array - } - // Since traverse returns the index of the node *after* it's pushed, - // and we start root traversal first, the root of the tree is actually the *last* node processed? - // Wait, traverse(root) calls traverse(child). - // nodes.push is CALLED at start of function. - // So Root is pushed FIRST. index 1. - // children (recursive) key pushed AFTER. - // So root_id is indeed 1. Verified. + if (root) root_id = traverse(root); const n_nodes = nodes.length; const max_children = Math.max(1, ...nodes.map(n => n.children.length)); - - // Helper to pad arrays - const pad = (arr: number[], len: number, val: number) => - [...arr, ...Array(Math.max(0, len - arr.length)).fill(val)]; + const pad = (arr: number[], len: number, val: number) => [...arr, ...Array(Math.max(0, len - arr.length)).fill(val)]; const node_kind = nodes.map(n => n.kind); const node_task_id = nodes.map(n => n.task_id); const node_n_children = nodes.map(n => n.children.length); - const node_children = nodes.map(n => pad(n.children, max_children, 0)); + const node_children = nodes.map(n => pad(n.children, max_children, 0)); const node_xor_probs = nodes.map(n => pad(n.xor_probs, max_children, 0.0)); + const node_loop_iters = nodes.map(n => n.loop_iters); - // Objective + // --- 5. Weights --- const obj = instance.objective || {}; - const w = obj.weights || {}; + const weightsObj = obj.weights || {}; + const qos_weights: number[] = []; + for (const feat of features) { + qos_weights.push(Number(weightsObj[feat] || 0.0)); + } - // --- 3. Constraints --- + // --- 6. Constraints --- const constraints = instance.constraints || []; - const attrMap: Record = { "cost": 1, "time": 2, "reliability": 3, "availability": 4, "security": 5 }; const opMap: Record = { "<=": 1, ">=": 2, "==": 3, "<": 4, ">": 5 }; const gc_attr: number[] = []; @@ -344,25 +432,29 @@ export class Solver { const dc_t2: number[] = []; for (const c of constraints) { + // Skip soft constraints for now + if (c.hard === false) continue; + if (c.kind === 'attribute_bound') { - const attr = attrMap[c.attribute_id]; + const attrIdx = featureMap[c.attribute_id]; const op = opMap[c.op]; - if (!attr || !op) continue; + if (!attrIdx || !op) continue; if (c.scope === 'global') { - gc_attr.push(attr); + gc_attr.push(attrIdx); gc_op.push(op); gc_val.push(c.value); } else if (c.scope === 'local' && c.task_id) { const tIdx = taskIdx[c.task_id]; if (tIdx) { lc_task.push(tIdx); - lc_attr.push(attr); + lc_attr.push(attrIdx); lc_op.push(op); lc_val.push(c.value); } } } else if (c.kind === 'dependency') { + // ... (Same as before) const tIndices = (c.tasks || []).map((tid: string) => taskIdx[tid]).filter((i: any) => i); if (tIndices.length < 2) continue; @@ -385,41 +477,33 @@ export class Solver { } return ` - % --- Generated Data --- root_id = ${root_id}; PROB_EPS = 1e-6; - COST_UB = ${COST_UB}; - TIME_UB = ${TIME_UB}; - n_tasks = ${n_tasks}; n_candidates = ${n_candidates}; n_nodes = ${n_nodes}; + n_qos = ${n_qos}; max_children = ${max_children}; max_cands_per_task = ${max_cands_per_task}; n_task_cands = ${fmt(n_task_cands)}; task_cands = ${fmt2d(task_cands)}; + cand_qos = ${fmt2d(cand_qos)}; candidate_provider = ${fmt(cand_provider)}; - c_cost = ${fmt(c_cost_arr)}; - c_time = ${fmt(c_time_arr)}; - c_rel = ${fmt(c_rel_arr)}; - c_avail = ${fmt(c_avail_arr)}; - c_sec = ${fmt(c_sec_arr)}; - node_kind = ${fmt(node_kind)}; node_task_id = ${fmt(node_task_id)}; node_n_children = ${fmt(node_n_children)}; node_children = ${fmt2d(node_children)}; node_xor_probs = ${fmt2d(node_xor_probs)}; + node_loop_iters = ${fmt(node_loop_iters)}; - w_cost = ${w.cost || 0.0}; - w_time = ${w.time || 0.0}; - w_rel = ${w.reliability || 0.0}; - w_avail = ${w.availability || 0.0}; - w_sec = ${w.security || 0.0}; + agg_policy = ${fmt2d(agg_policy)}; + + qos_weights = ${fmt(qos_weights)}; + qos_ub = ${fmt(qos_ub)}; n_global_constraints = ${gc_attr.length}; gc_attr = ${fmt(gc_attr)}; @@ -442,8 +526,9 @@ export class Solver { private getKind(k: string): number { if (k === 'TASK') return 1; if (k === 'SEQ') return 2; - if (k === 'AND_PAR') return 3; + if (k === 'AND') return 3; if (k === 'XOR') return 4; + if (k === 'LOOP') return 5; return 0; } @@ -498,7 +583,7 @@ export class Solver { const obj = instance.objective || {}; if (obj.type === 'weighted_sum' && obj.weights) { const weights = Object.values(obj.weights).map((w: any) => Number(w)); - const sum = weights.reduce((a, b) => a + b, 0); + const sum = weights.reduce((a, b) => a + Math.abs(b), 0); if (sum === 0 && weights.length > 0) { violations.push({ message: "Objective has all zero weights. Optimization effectively disabled.", diff --git a/engines/random-search/.classpath b/engines/random-search/.classpath new file mode 100644 index 0000000..18c4786 --- /dev/null +++ b/engines/random-search/.classpath @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engines/random-search/.gitignore b/engines/random-search/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/engines/random-search/.gitignore @@ -0,0 +1 @@ +/target diff --git a/engines/random-search/.project b/engines/random-search/.project new file mode 100644 index 0000000..92ec78a --- /dev/null +++ b/engines/random-search/.project @@ -0,0 +1,40 @@ + + + ManyObjectivesQoSawareCWSBinding + + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + org.eclipse.wst.common.project.facet.core.nature + + + + 1768938301184 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/engines/random-search/.settings/org.eclipse.core.resources.prefs b/engines/random-search/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..3622a30 --- /dev/null +++ b/engines/random-search/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +encoding//src/main/java=ISO-8859-1 +encoding/=ISO-8859-1 diff --git a/engines/random-search/.settings/org.eclipse.jdt.apt.core.prefs b/engines/random-search/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 0000000..d4313d4 --- /dev/null +++ b/engines/random-search/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=false diff --git a/engines/random-search/.settings/org.eclipse.jdt.core.prefs b/engines/random-search/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..6a09005 --- /dev/null +++ b/engines/random-search/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,12 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.processAnnotations=disabled +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/engines/random-search/.settings/org.eclipse.m2e.core.prefs b/engines/random-search/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/engines/random-search/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/engines/random-search/.settings/org.eclipse.wst.common.project.facet.core.xml b/engines/random-search/.settings/org.eclipse.wst.common.project.facet.core.xml new file mode 100644 index 0000000..bc0009a --- /dev/null +++ b/engines/random-search/.settings/org.eclipse.wst.common.project.facet.core.xml @@ -0,0 +1,4 @@ + + + + diff --git a/engines/random-search/Dockerfile b/engines/random-search/Dockerfile new file mode 100644 index 0000000..50611f6 --- /dev/null +++ b/engines/random-search/Dockerfile @@ -0,0 +1,14 @@ +FROM maven:3.8-openjdk-8 as builder +WORKDIR /app +COPY pom.xml . +COPY src ./src +# COPY lib ./lib # No lib present or empty +RUN mvn package -DskipTests + +FROM eclipse-temurin:8-jre +WORKDIR /app +RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/* +COPY --from=builder /app/target/ManyObjectivesQoSawareCWSBinding-0.0.1-SNAPSHOT.jar app.jar +COPY --from=builder /app/target/dependency /app/dependency +EXPOSE 8080 +CMD ["java", "-cp", "app.jar:dependency/*", "es.us.isa.qosawarewsbinding.api.Server"] diff --git a/engines/random-search/data/pruebatonta.txt b/engines/random-search/data/pruebatonta.txt new file mode 100644 index 0000000..64f9bac --- /dev/null +++ b/engines/random-search/data/pruebatonta.txt @@ -0,0 +1,111 @@ +%#============================= HEADER ======================================# +% This file contains an instance of the QoS-Aware Web Services Composition Problem +% FILE: pruebatonta.txt +% Created by: Jos Antonio Parejo Mestre +% Thu Mar 27 12:58:42 CET 2014 +% ---------------------- +% Problem Statistics: +% ---------------------- +% Number of activities: 4 +% Number of Candidate Services: 9 +% Number of Constraints: 0 +%#======================= COMPOSITION STRUCTURE =============================# +% Abstract Services: +%---------------------- +2 +t2 +t1 +% CompositionStructure: +%---------------------- +SEC[ + + BRANCH(0.6;0.4;)[ + SEC[t1, + ] + , + SEC[ + + LOOP(4000)[t2, + ] + , + ] + , + ] +, +] +%#======================= QOS MODEL =============================# +QoSModel{ + Properties{ + Availability:POSITIVEDouble[0.0,1.0] + Security:POSITIVEDouble[0.0,1.0] + ExecTime:NEGATIVEDouble[0.0,1.0] + Cost:NEGATIVEDouble[0.0,1.0] + Reliability:POSITIVEDouble[0.0,1.0] + } + AggregationFunctions( + Availability{ + Loop:PRODUCT + Branch:SUM + Flow:PRODUCT + Sequence:PRODUCT + } + Security{ + Loop:MIN + Branch:SUM + Flow:MIN + Sequence:MIN + } + ExecTime{ + Loop:SUM + Branch:SUM + Flow:MAX + Sequence:SUM + } + Cost{ + Loop:SUM + Branch:SUM + Flow:SUM + Sequence:SUM + } + Reliability{ + Loop:PRODUCT + Branch:SUM + Flow:PRODUCT + Sequence:PRODUCT + } + ) + Weights( + Availability:0.2 + Security:0.1 + ExecTime:0.2 + Cost:0.3 + Reliability:0.2 + ) +} +%#======================= CANDIDATE SERVICES =============================# +------------------------ +t2 +------------------------ +s23(Availability:0.5999999999999999,Security:0.24999999999999994,ExecTime:0.6666666666666666,Cost:0.7499999999999999,Reliability:0.0,) +s22(Availability:0.0,Security:1.0,ExecTime:0.0,Cost:1.0,Reliability:0.0,) +s21(Availability:0.0,Security:1.0,ExecTime:1.0,Cost:0.0,Reliability:1.0,) +------------------------ +t4 +------------------------ +s42(Availability:1.0,Security:0.0,ExecTime:0.33333333333333337,Cost:0.8749999999999999,Reliability:0.0,) +s41(Availability:0.0,Security:1.0,ExecTime:1.0,Cost:0.0,Reliability:1.0,) +------------------------ +t1 +------------------------ +s12(Availability:0.8,Security:1.0,ExecTime:0.8666666666666666,Cost:0.625,Reliability:0.0,) +s11(Availability:0.0,Security:1.0,ExecTime:0.0,Cost:1.0,Reliability:0.0,) +------------------------ +t3 +------------------------ +s32(Availability:1.0,Security:0.0,ExecTime:0.33333333333333337,Cost:0.8749999999999999,Reliability:0.0,) +s31(Availability:0.0,Security:1.0,ExecTime:1.0,Cost:0.0,Reliability:1.0,) +------------------------ +%#======================= CONSTRAINTS =============================# +0 +% ---------------------- +% ---------------------- diff --git a/engines/random-search/pom.xml b/engines/random-search/pom.xml new file mode 100644 index 0000000..e8233bc --- /dev/null +++ b/engines/random-search/pom.xml @@ -0,0 +1,49 @@ + + 4.0.0 + es.us.isa + ManyObjectivesQoSawareCWSBinding + 0.0.1-SNAPSHOT + + ISO-8859-1 + + + + commons-math + commons-math + 1.2 + + + com.google.code.gson + gson + 2.8.9 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.7 + 1.7 + + + + org.apache.maven.plugins + maven-dependency-plugin + + + package + + copy-dependencies + + + ${project.build.directory}/dependency + + + + + + + \ No newline at end of file diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/AbstractWebService.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/AbstractWebService.java new file mode 100644 index 0000000..0492e02 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/AbstractWebService.java @@ -0,0 +1,36 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding; + +import java.io.Serializable; + +/** + * + * @author japarejo + */ +public class AbstractWebService implements StructuralComponent, Serializable { + private String name; + public AbstractWebService(String name) + { + this.name=name; + } + + @Override + public String toString() + { + return name; + } + + + public String toStructuralString(String prefix) { + return prefix+name; + } + + public boolean isEmpty() { + return false; + } + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/Branch.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/Branch.java new file mode 100644 index 0000000..be05462 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/Branch.java @@ -0,0 +1,140 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * + * @author japarejo + */ +public class Branch implements CompositeStructuralComponent { + + protected Map branches; + protected List subComponents; + public Branch() + { + branches=new HashMap(); + subComponents=new LinkedList(); + } + + + public List getSubComponents() { + return subComponents; + } + + public void addBranch(StructuralComponent body, Double probability) + { + /*double scalingFactor=1.0/(1.0+probability.doubleValue()); + Double auxProbability=null; + for(StructuralComponent branch:branches.keySet()) + { + auxProbability=branches.get(branch); + branches.put(branch,auxProbability.doubleValue()*scalingFactor); + } + if(subComponents.size()==0) + branches.put(body,1.0); + else*/ + branches.put(body, probability); + subComponents.add(body); + } + + public void removeBrach(StructuralComponent body) + { + if(hasBranch(body)) + { + Double probability=branches.get(body); + branches.remove(body); + subComponents.remove(body); + if(probability.doubleValue()!=1.0) + { + double scalingFactor=1.0/(1.0-probability.doubleValue()); + for(StructuralComponent branch:branches.keySet()) + { + probability=branches.get(branch); + branches.put(branch,probability.doubleValue()*scalingFactor); + } + } + } + } + + public int numberOfBranches() + { + return subComponents.size(); + } + + private boolean hasBranch(StructuralComponent body) { + return subComponents.contains(body); + } + + + public Double getBranchProbability(StructuralComponent body) + { + Double result=new Double(0); + if(hasBranch(body)) + result=branches.get(body); + return result; + } + + + public Double getPonderation(StructuralComponent subcomponent) { + return getBranchProbability(subcomponent); + } + + + public String toStructuralString(String prefix) { + String newline = System.getProperty("line.separator"); + StringBuffer buffer=new StringBuffer(prefix+"BRANCH("); + for(StructuralComponent subComponent:subComponents) + buffer.append(branches.get(subComponent)+";"); + buffer.append(")["+newline); + String myprefix=prefix+" "; + for(StructuralComponent subComponent:subComponents) + buffer.append(subComponent.toStructuralString(myprefix)+newline+prefix+","+newline); + buffer.append(prefix+"]"); + return buffer.toString(); + } + + @Override + public String toString() + { + return toStructuralString(""); + } + + + public boolean isEmpty() { + boolean result=true; + for(StructuralComponent branch:subComponents){ + if(!branch.isEmpty()){ + result=false; + break; + } + } + return result; + } + + /*@Override + public void buildEvaluationMap(Map, Double> evaluationMap, Double currentValue) { + StructuralComponent component=null; + double probability=0; + for(String branchname:branches.keySet()) + { + component=branches.get(branchname); + probability=probabilities.get(branchname); + if(component instanceof CompositeStructuralComponent) + ((CompositeStructuralComponent)component).buildEvaluationMap(evaluationMap, probability*currentValue.doubleValue()); + else{ + List services=new ArrayList(1); + services.add((AbstractWebService)component); + evaluationMap.put(services,currentValue); + } + } + }*/ + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/CompositeStructuralComponent.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/CompositeStructuralComponent.java new file mode 100644 index 0000000..662d435 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/CompositeStructuralComponent.java @@ -0,0 +1,19 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding; + +import java.util.List; + +/** + * + * @author japarejo + */ +public interface CompositeStructuralComponent extends StructuralComponent { + + public List getSubComponents(); + public Double getPonderation(StructuralComponent subcomponent); + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/ConcreteWebService.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/ConcreteWebService.java new file mode 100644 index 0000000..85403fe --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/ConcreteWebService.java @@ -0,0 +1,53 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import es.us.isa.qosawarewsbinding.qos.QoSProperty; + +/** + * + * @author japarejo + */ +public class ConcreteWebService implements Serializable { + private String name; + private AbstractWebService abstractWebService; + protected Map qosmodel; + + + public ConcreteWebService(String name, AbstractWebService aws) + { + this.name=name; + this.abstractWebService=aws; + qosmodel=new HashMap(); + } + + public Object getQoSValue(QoSProperty property){ + return qosmodel.get(property); + } + + public void setQoSValue(QoSProperty property, Object value){ + qosmodel.put(property, value); + } + + public String getName() { + return name; + } + + public AbstractWebService getAbstractWebService() { + return abstractWebService; + } + + @Override + public String toString() + { + return name; + } + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/Flow.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/Flow.java new file mode 100644 index 0000000..e339aeb --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/Flow.java @@ -0,0 +1,67 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding; + +import java.util.List; + +/** + * + * @author japarejo + */ +public class Flow implements CompositeStructuralComponent { + + private List subcomponents; + + public Flow() { + subcomponents = new java.util.LinkedList(); + } + + public List getSubComponents() { + return subcomponents; + } + + + public Double getPonderation(StructuralComponent subcomponent) { + return 1.0; + } + + + public String toStructuralString(String prefix) { + String newline = System.getProperty("line.separator"); + StringBuffer buffer=new StringBuffer(prefix+"FLOW["+newline); + String myprefix=prefix+" ||=>"; + for(StructuralComponent subComponent:subcomponents) + buffer.append(subComponent.toStructuralString(myprefix)+","); + buffer.append("]"); + return buffer.toString(); + } + + public boolean isEmpty() { + boolean result=true; + for(StructuralComponent branch:subcomponents){ + if(!branch.isEmpty()){ + result=false; + break; + } + } + return result; + } + + /*@Override + public void buildEvaluationMap(Map, Double> evaluationMap, Double currentValue) { + List services=new LinkedList(); + for(StructuralComponent component:subcomponents) + { + if(component instanceof AbstractWebService) + { + services.add((AbstractWebService)component); + }else + ((CompositeStructuralComponent)component).buildEvaluationMap(evaluationMap,currentValue); + } + if(services.size()>0) + evaluationMap.put(services, currentValue); + }*/ +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/Loop.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/Loop.java new file mode 100644 index 0000000..04c7b56 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/Loop.java @@ -0,0 +1,98 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding; + +import java.util.LinkedList; +import java.util.List; + +/** + * + * @author japarejo + */ +public class Loop implements CompositeStructuralComponent { + + private int averageNumberOfIterations; + protected List subComponents; + public Loop(int averageNumberOfIterations) + { + this(averageNumberOfIterations,null); + } + public Loop(int averageNumberOfIterations, StructuralComponent component){ + this.averageNumberOfIterations=averageNumberOfIterations; + subComponents=new LinkedList(); + if(component!=null) + subComponents.add(component); + } + + + public List getSubComponents() { + return subComponents; + } + + public int getAverageNumberOfIterations() { + return averageNumberOfIterations; + } + + public void setAverageNumberOfIterations(int averageNumberOfIterations) { + this.averageNumberOfIterations = averageNumberOfIterations; + } + + + public Double getPonderation(StructuralComponent subcomponent) { + return (double)averageNumberOfIterations; + } + + + public String toStructuralString(String prefix) { + String newline = System.getProperty("line.separator"); + StringBuffer buffer=new StringBuffer(prefix+"LOOP("+averageNumberOfIterations+")["); + String myprefix=prefix+" "; + if(subComponents.size()>0) + { + if(!(subComponents.get(0) instanceof AbstractWebService)) + buffer.append(newline); + } + for(StructuralComponent subComponent:subComponents){ + if(subComponent instanceof CompositeStructuralComponent) + buffer.append(newline+subComponent.toStructuralString(myprefix)+newline+prefix+","); + else + buffer.append(subComponent.toStructuralString("")+","); + } + buffer.append(newline+prefix+"]"); + return buffer.toString(); + } + + public boolean isEmpty() { + boolean result=true; + for(StructuralComponent branch:subComponents){ + if(!branch.isEmpty()){ + result=false; + break; + } + } + return result; + } + + /*@Override + public void buildEvaluationMap(Map, Double> evaluationMap, Double currentValue) { + List services=new LinkedList(); + for(StructuralComponent component:subComponents) + { + if(component instanceof AbstractWebService) + { + services.add((AbstractWebService)component); + }else + ((CompositeStructuralComponent)component).buildEvaluationMap(evaluationMap,currentValue*averageNumberOfIterations); + } + if(services.size()>0) + evaluationMap.put(services, currentValue*averageNumberOfIterations); + }*/ + @Override + public String toString() + { + return toStructuralString(""); + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/Sequence.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/Sequence.java new file mode 100644 index 0000000..25d4636 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/Sequence.java @@ -0,0 +1,92 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding; + +import java.util.LinkedList; +import java.util.List; + +/** + * + * @author japarejo + */ +public class Sequence implements CompositeStructuralComponent { + protected List subComponents; + public Sequence() + { + subComponents=new LinkedList(); + } + + + public List getSubComponents() { + return subComponents; + } + /* + @Override + public void buildEvaluationMap(Map, Double> evaluationMap, Double currentValue) { + List services=new LinkedList(); + for(StructuralComponent component:subComponents) + { + if(component instanceof AbstractWebService) + { + services.add((AbstractWebService)component); + }else + ((CompositeStructuralComponent)component).buildEvaluationMap(evaluationMap,currentValue); + } + if(services.size()>0) + evaluationMap.put(services, currentValue); + }*/ + + + public Double getPonderation(StructuralComponent subcomponent) { + return 1.0; + } + + @Override + public String toString() + { + StringBuffer buffer=new StringBuffer("Sec["); + for(StructuralComponent subComponent:subComponents) + buffer.append(subComponent.toString()+","); + buffer.append("]"); + return buffer.toString(); + } + + + public String toStructuralString(String prefix) { + StringBuffer buffer=new StringBuffer(prefix+"SEC["); + String newline = System.getProperty("line.separator"); + String myprefix=prefix+" "; + if(subComponents.size()>0) + { + if(!(subComponents.get(0) instanceof AbstractWebService)) + buffer.append(newline); + } + boolean newlinegenerated=false; + for(StructuralComponent subComponent:subComponents) + if(subComponent instanceof CompositeStructuralComponent){ + buffer.append(newline+subComponent.toStructuralString(myprefix)+newline+prefix+","); + newlinegenerated=true; + }else + buffer.append(subComponent.toStructuralString("")+","); + buffer.append(newline+prefix+"]"); + return buffer.toString(); + } + + + public boolean isEmpty() { + boolean result=true; + for(StructuralComponent branch:subComponents){ + if(!branch.isEmpty()){ + result=false; + break; + } + } + return result; + } + + + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/StructuralComponent.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/StructuralComponent.java new file mode 100644 index 0000000..8a66d54 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/StructuralComponent.java @@ -0,0 +1,17 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding; + +import java.io.Serializable; + +/** + * + * @author japarejo + */ +public interface StructuralComponent extends Serializable { + public String toStructuralString(String prefix); + public boolean isEmpty(); +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/WSCompositionStructure.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/WSCompositionStructure.java new file mode 100644 index 0000000..230e21b --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/WSCompositionStructure.java @@ -0,0 +1,234 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding; + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * + * @author japarejo + */ +public class WSCompositionStructure implements Serializable{ + protected Set components; + protected StructuralComponent structure; + protected double numberOfexecutedTasks; + + public WSCompositionStructure(StructuralComponent structure) + { + this.structure=structure; + components=new HashSet(); + createComponents(); + numberOfexecutedTasks=numberOfExecutedTasks(structure); + } + + public Set getComponents() { + return components; + } + + public StructuralComponent getStructure() { + return structure; + } + + public int maxNestingLevel() { + return computeMaxNestingLevel(structure); + } + + private int NBuildingBlocks(Class aClass, StructuralComponent structure) { + int result=0; + if(structure instanceof CompositeStructuralComponent) + { + CompositeStructuralComponent compositeComponent=(CompositeStructuralComponent)structure; + for(StructuralComponent subComponent:compositeComponent.getSubComponents()) + { + result+=NBuildingBlocks(aClass, subComponent); + } + if(structure.getClass().equals(aClass)) + result++; + } + return result; + } + + private void addComponent(StructuralComponent structure) { + if(structure instanceof AbstractWebService) + components.add((AbstractWebService) structure); + else{ + Collection subcomponents=((CompositeStructuralComponent)structure).getSubComponents(); + for(StructuralComponent subcomponent:subcomponents) + addComponent(subcomponent); + } + } + + private int computeMaxNestingLevel(StructuralComponent structure) { + int result=0; + int auxNestingLevel; + if(structure instanceof CompositeStructuralComponent) + { + result=1; + CompositeStructuralComponent compositeComponent=(CompositeStructuralComponent)structure; + for(StructuralComponent subComponent:compositeComponent.getSubComponents()) + { + if(subComponent instanceof Sequence) + auxNestingLevel=computeMaxNestingLevel(subComponent); + else + auxNestingLevel=1+computeMaxNestingLevel(subComponent); + if(auxNestingLevel>result) + result=auxNestingLevel; + } + } + return result; + } + + private void createComponents() { + addComponent(structure); + } + + public int computeCyclomaticComplexity() + { + return computeCyclomaticComplexity(structure)+1; + } + + public int computeMaxNesting() + { + return computeMaxNestingLevel(structure); + } + + public int numberOfActivities() + { + return numberOfActivities(structure); + } + + private int computeCyclomaticComplexity(StructuralComponent component) + { + int result=0; + if(component instanceof CompositeStructuralComponent) + { + CompositeStructuralComponent compositeComponent=(CompositeStructuralComponent)component; + if(compositeComponent instanceof Loop){ + result=1; + for(StructuralComponent subComponent:compositeComponent.getSubComponents()) + { + result+=computeCyclomaticComplexity(subComponent); + } + }else if(compositeComponent instanceof Branch){ + result=compositeComponent.getSubComponents().size()-1; + for(StructuralComponent subComponent:compositeComponent.getSubComponents()) + { + result+=computeCyclomaticComplexity(subComponent); + } + }else if(compositeComponent instanceof Flow){ + result=compositeComponent.getSubComponents().size()-1; + for(StructuralComponent subComponent:compositeComponent.getSubComponents()) + { + result+=computeCyclomaticComplexity(subComponent); + } + }else if(component instanceof Sequence){ + for(StructuralComponent subComponent:compositeComponent.getSubComponents()) + { + result+=computeCyclomaticComplexity(subComponent); + } + } + } + return result; + } + + private int numberOfActivities(StructuralComponent structure) { + int result=1; + if(structure instanceof CompositeStructuralComponent) + { + CompositeStructuralComponent compositeComponent=(CompositeStructuralComponent)structure; + for(StructuralComponent subComponent:compositeComponent.getSubComponents()) + { + result+=numberOfActivities(subComponent); + } + if(structure instanceof Sequence) + result=result-1; + if(structure instanceof Flow) + result=result-1; + } + return result; + } + + private double numberOfExecutedTasks(StructuralComponent structure) { + double result=0; + if(structure instanceof CompositeStructuralComponent) + { + CompositeStructuralComponent compositeComponent=(CompositeStructuralComponent)structure; + for(StructuralComponent subComponent:compositeComponent.getSubComponents()) + { + if(subComponent instanceof AbstractWebService) + result+=compositeComponent.getPonderation(subComponent); + else + result+=numberOfExecutedTasks(subComponent)*compositeComponent.getPonderation(subComponent); + } + }else + result=1; + return result; + } + + private int numberOfTaskActivities(StructuralComponent structure) + { + int result=0; + if(structure instanceof CompositeStructuralComponent) + { + CompositeStructuralComponent compositeComponent=(CompositeStructuralComponent)structure; + for(StructuralComponent subComponent:compositeComponent.getSubComponents()) + { + if(subComponent instanceof CompositeStructuralComponent) + result+=numberOfTaskActivities(subComponent); + else + result++; + } + }else{ + result=1; + } + return result; + } + + public double perecentageOfControlFlowActivities() + { + double percentageOfTaskActivities=((double)(numberOfTaskActivities(structure)))/((double)(numberOfActivities(structure))); + return 1.0 - percentageOfTaskActivities; + } + + public double numberOfExecutedTasks() + { + if(numberOfexecutedTasks==Double.MIN_VALUE) + numberOfexecutedTasks=numberOfExecutedTasks(structure); + return numberOfexecutedTasks; + } + + public double percentageOfLoops() + { + double result=0; + double nloops=(double)NBuildingBlocks(Loop.class, structure); + result=nloops/((double)(numberOfActivities(structure)-numberOfTaskActivities(structure))); + return result; + } + public double averageIterationsPerLoop() + { + double nloops=(double)NBuildingBlocks(Loop.class, structure); + double nTotalIterations=(double)NtotalIterations(structure); + return nTotalIterations/nloops; + } + public double NtotalIterations(StructuralComponent structure) + { + double result=0; + if(structure instanceof CompositeStructuralComponent) + { + CompositeStructuralComponent compositeComponent=(CompositeStructuralComponent)structure; + for(StructuralComponent subComponent:compositeComponent.getSubComponents()) + { + result+=NtotalIterations(subComponent); + } + if(structure instanceof Loop) + result+=((Loop)structure).getAverageNumberOfIterations(); + } + return result; + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/api/Controller.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/api/Controller.java new file mode 100644 index 0000000..c101f6f --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/api/Controller.java @@ -0,0 +1,359 @@ +package es.us.isa.qosawarewsbinding.api; + +import com.google.gson.Gson; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import es.us.isa.qosawarewsbinding.AbstractWebService; +import es.us.isa.qosawarewsbinding.ConcreteWebService; +import es.us.isa.qosawarewsbinding.StructuralComponent; +import es.us.isa.qosawarewsbinding.WSCompositionStructure; +import es.us.isa.qosawarewsbinding.api.dto.SolveRequest; +import es.us.isa.qosawarewsbinding.api.dto.SolveResponse; +import es.us.isa.qosawarewsbinding.problem.QoSAwareWSCompositionProblem; +import es.us.isa.qosawarewsbinding.problem.WSCompositionQoSModel; +import es.us.isa.qosawarewsbinding.problem.GlobalQoSWSCompositionConstraint; +import es.us.isa.qosawarewsbinding.problem.BinaryOperator; +import es.us.isa.qosawarewsbinding.qos.QoSProperty; +import es.us.isa.qosawarewsbinding.qos.QoSPropertyType; +import es.us.isa.qosawarewsbinding.qos.aggretation.*; +import es.us.isa.qosawarewsbinding.solution.QoSAwareWSCompositionSolution; +import es.us.isa.qosawarewsbinding.solution.vector.*; +import es.us.isa.qosawarewsbinding.*; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.*; + +public class Controller implements HttpHandler { + private Gson gson = new Gson(); + + @Override + public void handle(HttpExchange exchange) throws IOException { + if (!"POST".equalsIgnoreCase(exchange.getRequestMethod())) { + exchange.sendResponseHeaders(405, -1); + return; + } + + try { + SolveRequest req = gson.fromJson(new InputStreamReader(exchange.getRequestBody()), SolveRequest.class); + SolveResponse resp = process(req); + + String jsonResp = gson.toJson(resp); + exchange.getResponseHeaders().set("Content-Type", "application/json"); + exchange.sendResponseHeaders(200, jsonResp.length()); + OutputStream os = exchange.getResponseBody(); + os.write(jsonResp.getBytes()); + os.close(); + } catch (Exception e) { + e.printStackTrace(); + String error = "{\"error\": \"" + e.getMessage() + "\"}"; + exchange.sendResponseHeaders(500, error.length()); + OutputStream os = exchange.getResponseBody(); + os.write(error.getBytes()); + os.close(); + } + } + + private SolveResponse process(SolveRequest req) { + // 1. Map Structure + Map taskMap = new HashMap(); + StructuralComponent root = mapNode(req.composition.root, taskMap); + WSCompositionStructure structure = new WSCompositionStructure(root); + + // 2. Map QoS Model + Map> propertyMap = new HashMap>(); + Set qosProperties = new HashSet(); + + for (Map.Entry entry : req.features.properties.entrySet()) { + QoSPropertyType type = "maximize".equals(entry.getValue().direction) ? QoSPropertyType.POSITIVE + : QoSPropertyType.NEGATIVE; + // Assuming 0.0-1.0 domain for normalization + QoSProperty prop = new QoSProperty(entry.getKey(), + new es.us.isa.qosawarewsbinding.util.BoundedDomain(0.0, 1.0), type); + qosProperties.add(prop); + propertyMap.put(entry.getKey(), prop); + } + WSCompositionQoSModel qosModel = new WSCompositionQoSModel(qosProperties); + + // Map weights + Map weights = new HashMap(); + for (Map.Entry entry : req.features.weights.entrySet()) { + weights.put(propertyMap.get(entry.getKey()), entry.getValue()); + } + qosModel.setQosPropertiesWeights(weights); + + // Map aggregation functions + for (Map.Entry entry : req.features.aggregation.entrySet()) { + QoSProperty prop = propertyMap.get(entry.getKey()); + SolveRequest.AggregationPolicy policy = entry.getValue(); + + qosModel.setAggregationFunction(prop, Sequence.class, getAggFunc(policy.seq)); + qosModel.setAggregationFunction(prop, Flow.class, getAggFunc(policy.flow)); + qosModel.setAggregationFunction(prop, Branch.class, getAggFunc(policy.branch)); + qosModel.setAggregationFunction(prop, Loop.class, getAggFunc(policy.loop)); + } + + // 3. Map Market + Map> market = new HashMap>(); + // Need to find all AbstractWebServices (Tasks) in structure + + // collectTasks called during mapping + + for (Map.Entry entry : taskMap.entrySet()) { + String taskId = entry.getKey(); + AbstractWebService aws = entry.getValue(); + Set candidates = new HashSet(); + + SolveRequest.ServiceCandidates sc = req.market.get(taskId); + if (sc != null && sc.services != null) { + for (SolveRequest.Service s : sc.services) { + ConcreteWebService cws = new ConcreteWebService(s.id, aws); + + // Iterate over all expected properties to ensure completeness + for (QoSProperty p : propertyMap.values()) { + Double val = s.features.get(p.getName()); + if (val == null) { + // Assign default based on type + if (p.getType() == QoSPropertyType.POSITIVE) { + val = 0.0; + } else { + // For minimization, we'd ideally want a "bad" value. + // But Double.MAX_VALUE might skew normalization too much. + // Let's use a reasonably high value or 0 if that's safer for now, + // but for robustness, 999999.0 is a placeholder "bad" value. + val = 999999.0; + } + System.err.println("Warning: Missing QoS value for " + p.getName() + " in service " + s.id + + " (Task " + taskId + "). Using default: " + val); + } + cws.setQoSValue(p, val); + } + candidates.add(cws); + } + } + market.put(aws, candidates); + } + + // 4. Create Problem (constraints will be attached after instantiation) + QoSAwareWSCompositionProblem problem = new QoSAwareWSCompositionProblem( + structure, + market, + qosModel, + new LinkedList() + ); + + // 4.1 Map Global Attribute Bound Constraints (if any) + if (req.constraints != null) { + for (SolveRequest.Constraint c : req.constraints) { + if (c == null) { + continue; + } + if (c.kind == null || !"attribute_bound".equalsIgnoreCase(c.kind)) { + continue; + } + if (c.scope != null && !"global".equalsIgnoreCase(c.scope)) { + continue; + } + if (c.attribute_id == null || c.op == null || c.value == null) { + continue; + } + + QoSProperty prop = propertyMap.get(c.attribute_id); + if (prop == null) { + continue; + } + + BinaryOperator op; + if ("==".equals(c.op)) { + op = BinaryOperator.EQUAL; + } else if ("!=".equals(c.op)) { + op = BinaryOperator.DISTINCT; + } else if (">".equals(c.op)) { + op = BinaryOperator.GREATER; + } else if (">=".equals(c.op)) { + op = BinaryOperator.GREATEREQUAL; + } else if ("<".equals(c.op)) { + op = BinaryOperator.LOWER; + } else if ("<=".equals(c.op)) { + op = BinaryOperator.LOWEREQUAL; + } else { + continue; + } + + problem.getConstraints().add(new GlobalQoSWSCompositionConstraint(problem, prop, c.value, op)); + } + } + + // 5. Solve (Simple Random Search) + int iterations = 1000; + if (req.config != null && req.config.max_iterations > 0) { + iterations = req.config.max_iterations; + } + + long start = System.currentTimeMillis(); + QoSAwareWSCompositionSolution bestSol = solveSimple(problem, iterations); + long end = System.currentTimeMillis(); + + // 6. Map Response + SolveResponse resp = new SolveResponse(); + resp.execution_time = end - start; + resp.iterations_count = iterations; // Report actual iterations used + resp.status = "optimized"; + resp.selection = new HashMap(); + resp.aggregated_features = new HashMap(); + + // Populate selection + if (bestSol instanceof QoSAwareWSCompositionVectorSolution) { + QoSAwareWSCompositionVectorSolution vecSol = (QoSAwareWSCompositionVectorSolution) bestSol; + // Need to map back AWS -> CWS ID + // The Solution interface doesn't expose map easily, but VectorSolution does + // Actually VectorSolution stores selection in a Map + // But it is protected or invalid access? + // Let's assume we can get it or we need a way. + // VectorSolution has getSelectedService(AbstractWebService) ?? + // Checking source... it extends AbstractSolution. + } + + // Workaround: We need to modify QoSAwareWSCompositionVectorSolution or inspect + // it. + // It has getService(AbstractWebService) ? + // I will check AbstractSolution source code if I can. + // For now, assume a method to extract selection. + + // Evaluate QoS + for (QoSProperty p : propertyMap.values()) { + double val = qosModel.evaluate(bestSol, p, structure); + resp.aggregated_features.put(p.getName(), val); + } + + // Populate selection map + for (Map.Entry entry : taskMap.entrySet()) { + String taskId = entry.getKey(); + AbstractWebService aws = entry.getValue(); + ConcreteWebService cws = bestSol.getSelectedService(aws); + if (cws != null) { + resp.selection.put(taskId, cws.getName()); + } + } + + return resp; + } + + private AggregationFunction getAggFunc(String name) { + if ("sum".equalsIgnoreCase(name)) + return SumatoryAggregationFunction.getInstance(); + if ("product".equalsIgnoreCase(name)) + return ProductoryAggregationFunction.getInstance(); + if ("max".equalsIgnoreCase(name)) + return MaxAggregationFunction.getInstance(); + if ("min".equalsIgnoreCase(name)) + return MinAggregationFunction.getInstance(); + return SumatoryAggregationFunction.getInstance(); // default + } + + private void collectTasks(StructuralComponent node, Map map) { + if (node instanceof AbstractWebService) { + // How do we know the ID? We constructed it with ID. + // AbstractWebService(String id). + // But AbstractWebService constructor takes 'id'. + // I need to store the mapping. + // Actually I should store mapping when creating node. + // AbstractWebService does not expose ID easily? It inherits from + // StructuralComponent ?? + // StructuralComponent doesn't seem to have ID accessor? + // Checking source needed. + // Assuming toString() or similar return ID, or I map objects. + // Best way: Map in context? + } + if (node instanceof CompositeStructuralComponent) { + for (StructuralComponent child : ((CompositeStructuralComponent) node).getSubComponents()) { + collectTasks(child, map); + } + } + } + + private StructuralComponent mapNode(SolveRequest.Node node, Map taskMap) { + if (node == null) { + return null; + } + if ("TASK".equals(node.kind)) { + AbstractWebService aws = new AbstractWebService(node.task_id); + taskMap.put(node.task_id, aws); + return aws; + } else if ("SEQ".equals(node.kind)) { + Sequence seq = new Sequence(); + if (node.children != null) { + for (SolveRequest.Node child : node.children) { + StructuralComponent sc = mapNode(child, taskMap); + if (sc != null) { + seq.getSubComponents().add(sc); + } + } + } + return seq; + } else if ("AND".equals(node.kind)) { + Flow flow = new Flow(); + if (node.children != null) { + for (SolveRequest.Node child : node.children) { + StructuralComponent sc = mapNode(child, taskMap); + if (sc != null) { + flow.getSubComponents().add(sc); + } + } + } + return flow; + } else if ("XOR".equals(node.kind)) { + Branch branch = new Branch(); + if (node.branches != null) { + for (SolveRequest.Branch b : node.branches) { + StructuralComponent child = mapNode(b.child, taskMap); + if (child != null) { + branch.addBranch(child, b.p); + } + } + } + return branch; + } else if ("LOOP".equals(node.kind)) { + int iterations = 1; + if (node.expected_iterations != null) { + iterations = (int) Math.round(node.expected_iterations.doubleValue()); + if (iterations < 0) { + iterations = 0; + } + } + Loop loop = new Loop(iterations); + if (node.body != null) { + StructuralComponent body = mapNode(node.body, taskMap); + if (body != null) { + loop.getSubComponents().add(body); + } + } + return loop; + } + return null; + } + + private QoSAwareWSCompositionSolution solveSimple(QoSAwareWSCompositionProblem problem, int iterations) { + // Simple Random Search + // Initialize with default (all 0 index) or random + QoSAwareWSCompositionSolution best = new QoSAwareWSCompositionVectorSolution(problem); + // best = best.createRandom(); // Maybe start random? + double bestFitness = problem.fitness(best); + + for (int i = 0; i < iterations; i++) { + // Create a new random solution + QoSAwareWSCompositionSolution sol = (QoSAwareWSCompositionSolution) new QoSAwareWSCompositionVectorSolution( + problem).createRandom(); + + double f = problem.fitness(sol); + + if (f < bestFitness) { + best = sol; + bestFitness = f; + } + } + return best; + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/api/Server.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/api/Server.java new file mode 100644 index 0000000..9af0bd1 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/api/Server.java @@ -0,0 +1,29 @@ +package es.us.isa.qosawarewsbinding.api; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.concurrent.Executors; + +public class Server { + public static void main(String[] args) throws IOException { + int port = 8080; + HttpServer server = HttpServer.create(new InetSocketAddress(port), 0); + server.createContext("/health", new HttpHandler() { + @Override + public void handle(HttpExchange exchange) throws IOException { + byte[] body = "{\"status\":\"ok\"}".getBytes("UTF-8"); + exchange.getResponseHeaders().add("Content-Type", "application/json"); + exchange.sendResponseHeaders(200, body.length); + exchange.getResponseBody().write(body); + exchange.close(); + } + }); + server.createContext("/solve", new Controller()); + server.setExecutor(Executors.newCachedThreadPool()); + System.out.println("Many-OBJ Service started on port " + port); + server.start(); + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/api/dto/SolveRequest.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/api/dto/SolveRequest.java new file mode 100644 index 0000000..d6ef63d --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/api/dto/SolveRequest.java @@ -0,0 +1,76 @@ +package es.us.isa.qosawarewsbinding.api.dto; + +import java.util.List; +import java.util.Map; + +public class SolveRequest { + public String id; + public CompositionStructure composition; + public Map market; + public QoSModel features; + public SolvingConfig config; + public List constraints; + + public static class CompositionStructure { + public String type; // "structured" + public Node root; + } + + public static class Node { + public String id; + public String kind; // TASK, SEQ, AND, XOR, LOOP + public String task_id; // if TASK + public List children; // if SEQ, AND + public List branches; // if XOR + public Node body; // if LOOP + public Double expected_iterations; // if LOOP + } + + public static class Branch { + public double p; + public Node child; + } + + public static class ServiceCandidates { + public List services; + } + + public static class Service { + public String id; // service concrete ID (e.g. "s11") + public String name; + public Map features; + } + + public static class QoSModel { + public Map properties; + public Map weights; + public Map aggregation; + } + + public static class QoSPropertyDef { + public String direction; // "minimize", "maximize" + } + + public static class AggregationPolicy { + // Simple map from operator (seq, flow, etc.) to function name (sum, max, etc.) + public String seq; + public String flow; // "and" in request mapped to "flow" in engine + public String branch; // "xor" + public String loop; + } + + public static class SolvingConfig { + public int max_iterations; + public int population_size; + } + + public static class Constraint { + public String id; + public String kind; // "attribute_bound" + public String scope; // "global" + public String attribute_id; + public String op; // <=, <, >=, >, ==, != + public Double value; + public Boolean hard; + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/api/dto/SolveResponse.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/api/dto/SolveResponse.java new file mode 100644 index 0000000..d09e047 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/api/dto/SolveResponse.java @@ -0,0 +1,13 @@ +package es.us.isa.qosawarewsbinding.api.dto; + +import java.util.Map; + +public class SolveResponse { + public String status; // optimized, error + public Map selection; // task_id -> service_id + public Map aggregated_features; // aggregated qos + public Object metadata; + public String error; + public Long execution_time; + public Integer iterations_count; +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/BinaryOperator.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/BinaryOperator.java new file mode 100644 index 0000000..4bfdf8a --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/BinaryOperator.java @@ -0,0 +1,12 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.problem; + +/** + * + * @author japarejo + */ +public enum BinaryOperator {EQUAL, DISTINCT(), GREATER,GREATEREQUAL,LOWER,LOWEREQUAL} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/Constraint.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/Constraint.java new file mode 100644 index 0000000..d228d9c --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/Constraint.java @@ -0,0 +1,8 @@ +package es.us.isa.qosawarewsbinding.problem; + +public interface Constraint { + public boolean meets (X solution); + public double meetingDistance (X solution); + +} + diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/EvaluationCountProblem.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/EvaluationCountProblem.java new file mode 100644 index 0000000..d7aa8b9 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/EvaluationCountProblem.java @@ -0,0 +1,38 @@ +package es.us.isa.qosawarewsbinding.problem; + +import es.us.isa.qosawarewsbinding.solution.Solution; + +public abstract class EvaluationCountProblem implements Problem { + private long fitnessEvaluationCount; + private long feasibilityEvaluationCount; + + public EvaluationCountProblem() + { + fitnessEvaluationCount=0; + feasibilityEvaluationCount=0; + } + + + public final double fitness(Solution sol) { + fitnessEvaluationCount++; + return computeFitness(sol); + } + + public final boolean feasible(Solution sol) { + feasibilityEvaluationCount++; + return computeFeasibility(sol); + } + + protected abstract boolean computeFeasibility(Solution sol); + + protected abstract double computeFitness(Solution sol); + + public long getFitnessEvaluationCount() { + return fitnessEvaluationCount; + } + + public long getFeasibilityEvaluationCount() { + return feasibilityEvaluationCount; + } + +} \ No newline at end of file diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/ExecutionPath.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/ExecutionPath.java new file mode 100644 index 0000000..194596b --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/ExecutionPath.java @@ -0,0 +1,76 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.problem; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import es.us.isa.qosawarewsbinding.AbstractWebService; + +/** + * + * @author japarejo + */ +public class ExecutionPath { + private QoSAwareWSCompositionProblem problem; + private List executedTasks; + private double probabilityOfExecution; + + public ExecutionPath(QoSAwareWSCompositionProblem problem, List execTasks,double prob) + { + this.problem=problem; + this.executedTasks=new ArrayList(execTasks.size()); + this.executedTasks.addAll(execTasks); + this.probabilityOfExecution=prob; + } + + public ExecutionPath(QoSAwareWSCompositionProblem problem, double prob) + { + this.problem=problem; + this.executedTasks=new LinkedList(); + this.probabilityOfExecution=prob; + } + + public QoSAwareWSCompositionProblem getProblem() { + return problem; + } + + public List getExecutedTasks() { + return executedTasks; + } + + public double getProbabilityOfExecution() { + return probabilityOfExecution; + } + + public void setProbabilityOfExecution(double pk) { + this.probabilityOfExecution = pk; + } + + @Override + public boolean equals(Object obj) + { + boolean result=false; + if(obj instanceof ExecutionPath) + { + ExecutionPath myobj=(ExecutionPath)obj; + result=(problem==myobj.getProblem()); + result=result && (probabilityOfExecution==myobj.getProbabilityOfExecution()); + result=(result && executedTasks.equals(myobj.getExecutedTasks())); + } + return result; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 29 * hash + (this.problem != null ? this.problem.hashCode() : 0); + hash = 29 * hash + (this.executedTasks != null ? this.executedTasks.hashCode() : 0); + hash = 29 * hash + (int) (Double.doubleToLongBits(this.probabilityOfExecution) ^ (Double.doubleToLongBits(this.probabilityOfExecution) >>> 32)); + return hash; + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/ExecutionPathsBuilder.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/ExecutionPathsBuilder.java new file mode 100644 index 0000000..444f2d4 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/ExecutionPathsBuilder.java @@ -0,0 +1,16 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.problem; + +import java.util.Set; + +/** + * + * @author japarejo + */ +public interface ExecutionPathsBuilder{ + public Set buildPaths(QoSAwareWSCompositionProblem aThis); +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/FeasibilityAwareProblem.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/FeasibilityAwareProblem.java new file mode 100644 index 0000000..047b98e --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/FeasibilityAwareProblem.java @@ -0,0 +1,46 @@ +package es.us.isa.qosawarewsbinding.problem; + +import es.us.isa.qosawarewsbinding.solution.Solution; + +public abstract class FeasibilityAwareProblem extends EvaluationCountProblem { + + private UnfeasibilityPenalizator penalizator; + + public FeasibilityAwareProblem() + { + penalizator=null; + } + + public FeasibilityAwareProblem(UnfeasibilityPenalizator penalizator) + { + this.penalizator=penalizator; + } + + + @Override + protected double computeFitness(Solution sol) { + double result=feasibilityFreeFitness(sol); + if(penalizator!=null) + result=getPenalizator().penalize(result,feasibilityDistance(sol)); + return result; + } + + public abstract double feasibilityDistance(Solution sol); + + public abstract double feasibilityFreeFitness(Solution sol); + + @Override + protected boolean computeFeasibility(Solution sol) { + return feasibilityDistance(sol)>0; + } + + /** + * @return the penalizator + */ + public UnfeasibilityPenalizator getPenalizator() { + return penalizator; + } + + +} + diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/GlobalQoSWSCompositionConstraint.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/GlobalQoSWSCompositionConstraint.java new file mode 100644 index 0000000..def3607 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/GlobalQoSWSCompositionConstraint.java @@ -0,0 +1,113 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.problem; + +import java.io.Serializable; + +import es.us.isa.qosawarewsbinding.qos.QoSProperty; +import es.us.isa.qosawarewsbinding.solution.QoSAwareWSCompositionSolution; + +/** + * + * @author japarejo + */ +public class GlobalQoSWSCompositionConstraint extends WSCompositionConstraint implements Serializable { + private QoSProperty property; + private BinaryOperator operator; + private Double value; + + public GlobalQoSWSCompositionConstraint(QoSAwareWSCompositionProblem problem, QoSProperty property, Double value, BinaryOperator operator) + { + super(problem); + if(problem!=null){ + if(problem.getQosmodel().getQosProperties().contains(property)) + { + this.property=property; + this.operator=operator; + this.value=value; + }else + throw new IllegalArgumentException("Property must be part of the QoS model of the problem at hand."); + }else{ + this.property=property; + this.operator=operator; + this.value=value; + } + } + + @Override + public boolean meets(QoSAwareWSCompositionSolution solution) { + return meetingDistance(solution)==0; + } + + @Override + public double meetingDistance(QoSAwareWSCompositionSolution solution) { + Double currentValue=problem.getQosmodel().evaluate(solution,getProperty(),problem.getStructure()); + return meetingDistance(currentValue); + } + + private double meetingDistance(Double currentValue) { + double result=getValue().doubleValue()-currentValue.doubleValue(); + if(getOperator()==BinaryOperator.EQUAL){ + result=Math.abs(result); + }else if(getOperator()==BinaryOperator.DISTINCT){ + if(result==0) + result=1; + else + result=0; + }else if(getOperator()==BinaryOperator.GREATER){ + if(result<0) + result=0; + else if(result==0) + result=Double.MIN_VALUE; + }else if(getOperator()==BinaryOperator.GREATEREQUAL){ + if(result<0) + result=0; + }else if(getOperator()==BinaryOperator.LOWER){ + if(result>0) + result=0; + else if(result<0) + result=Math.abs(result); + else + result=Double.MIN_VALUE; + }else if(getOperator()==BinaryOperator.LOWEREQUAL){ + if(result>0) + result=0; + else if(result<0) + result=Math.abs(result); + } + return result; + } + + public String toString() + { + String result=getOperator()+"("+getProperty().getName()+","+getValue()+")"; + return result; + } + + public QoSProperty getProperty() { + return property; + } + + public void setProperty(QoSProperty property) { + this.property = property; + } + + public BinaryOperator getOperator() { + return operator; + } + + public void setOperator(BinaryOperator operator) { + this.operator = operator; + } + + public Double getValue() { + return value; + } + + public void setValue(Double value) { + this.value = value; + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/LoopPeelingExecutionPathBuilder.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/LoopPeelingExecutionPathBuilder.java new file mode 100644 index 0000000..3eb8e33 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/LoopPeelingExecutionPathBuilder.java @@ -0,0 +1,119 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.problem; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import es.us.isa.qosawarewsbinding.AbstractWebService; +import es.us.isa.qosawarewsbinding.Branch; +import es.us.isa.qosawarewsbinding.Loop; +import es.us.isa.qosawarewsbinding.Sequence; +import es.us.isa.qosawarewsbinding.StructuralComponent; +import es.us.isa.qosawarewsbinding.WSCompositionStructure; + +/** + * + * @author japarejo + */ +public class LoopPeelingExecutionPathBuilder implements ExecutionPathsBuilder { + + public Set buildPaths(QoSAwareWSCompositionProblem aThis) { + Set execPaths=new HashSet(); + buildPaths(aThis.getStructure(),execPaths,aThis); + return execPaths; + } + + private void buildPaths(WSCompositionStructure structure, Set execPaths, QoSAwareWSCompositionProblem aThis) { + List previous=new LinkedList(); + List components=((Sequence)structure.getStructure()).getSubComponents(); + buildPaths(previous,components,0,1.0,execPaths,aThis); + } + + private void buildPaths(List previous, List components, int i, double d, Set execPaths, QoSAwareWSCompositionProblem aThis) { + if(i subComponents=((Sequence)comp).getSubComponents(); + List newComponents=createComponentsSec(subComponents,components,1,i); + buildPaths(previous,newComponents,i,d,execPaths,aThis); + }else if(comp instanceof Loop){ + List subComponents=((Loop)comp).getSubComponents(); + List newComponents=null; + for(int j=0;j<=((Loop)comp).getAverageNumberOfIterations();j++){ + newComponents=createComponentsLoops(subComponents,components,j,i); + buildPaths(previous,newComponents,i,d,execPaths,aThis); + } + }else if(comp instanceof Branch) + { + for(StructuralComponent sc:((Branch)comp).getSubComponents()) + { + components.set(i, sc); + buildPaths(previous,components,i,d*((Branch)comp).getBranchProbability(sc),execPaths,aThis); + } + } + }else{ + execPaths.add(new ExecutionPath(aThis, previous,d)); + } + } + + private List createComponentsLoops(List subComponents, List components, int i, int j) { + List result=new LinkedList(); + for(int k=0;k createComponentsSec(List subComponents, List components, int i, int j) { + List result=new LinkedList(); + for(int k=0;k buildPaths(QoSAwareWSCompositionProblem aThis) { + Set execPaths=new HashSet(); + buildPaths(aThis.getStructure(),execPaths,aThis); + return execPaths; + } + + private void buildPaths(WSCompositionStructure structure, Set execPaths,QoSAwareWSCompositionProblem aThis) { + List previous=new LinkedList(); + List components=((Sequence)structure.getStructure()).getSubComponents(); + buildPaths(previous,components,0,1.0,execPaths,aThis); + } + + private void buildPaths(List previous, List components, int i, double d, Set execPaths,QoSAwareWSCompositionProblem aThis) { + if(i subComponents=((Sequence)comp).getSubComponents(); + List newComponents=createComponents(subComponents,components,1,i); + buildPaths(previous,newComponents,i,d,execPaths,aThis); + }else if(comp instanceof Loop){ + List subComponents=((Loop)comp).getSubComponents(); + List newComponents=null; + for(int j=0;j<=((Loop)comp).getAverageNumberOfIterations();j++){ + newComponents=createComponents(subComponents,components,j,i); + buildPaths(previous,newComponents,i,d,execPaths,aThis); + } + }else if(comp instanceof Branch) + { + for(StructuralComponent sc:((Branch)comp).getSubComponents()) + { + components.set(i, sc); + buildPaths(previous,components,i,d*((Branch)comp).getBranchProbability(sc),execPaths,aThis); + } + } + }else{ + execPaths.add(new ExecutionPath(aThis, previous,d)); + } + } + + private List createComponents(List subComponents, List components, int j, int i) { + List result=new LinkedList(); + for(int k=0;k + * NOTA: se recomienda que la descripcin contenga, los parmetros esenciales que + * determinan el tamao concreto de la instancia del problem que el objeto representa + * y todos los parmetros ms que sean necesarios para describir el problem separados + * por comas. + */ + public String getDescription(); + /** This method provides the name of the problem type e.g.: "TSP","SAT","QAP",etc. + * if you are creating your own problem you should ensure its problem is unique + * in your FOM instance. + * @return name of the problem type as string. + */ + public String getProblemType(); + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/ProblemReaderAndWriter.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/ProblemReaderAndWriter.java new file mode 100644 index 0000000..56d7dbc --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/ProblemReaderAndWriter.java @@ -0,0 +1,558 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.problem; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import es.us.isa.qosawarewsbinding.AbstractWebService; +import es.us.isa.qosawarewsbinding.Branch; +import es.us.isa.qosawarewsbinding.ConcreteWebService; +import es.us.isa.qosawarewsbinding.Flow; +import es.us.isa.qosawarewsbinding.Loop; +import es.us.isa.qosawarewsbinding.Sequence; +import es.us.isa.qosawarewsbinding.StructuralComponent; +import es.us.isa.qosawarewsbinding.WSCompositionStructure; +import es.us.isa.qosawarewsbinding.qos.QoSProperty; +import es.us.isa.qosawarewsbinding.qos.QoSPropertyType; +import es.us.isa.qosawarewsbinding.qos.aggretation.AggregationFunction; +import es.us.isa.qosawarewsbinding.qos.aggretation.AverageAggregationFunction; +import es.us.isa.qosawarewsbinding.qos.aggretation.MaxAggregationFunction; +import es.us.isa.qosawarewsbinding.qos.aggretation.MaxAverageAggregationFunction; +import es.us.isa.qosawarewsbinding.qos.aggretation.MinAggregationFunction; +import es.us.isa.qosawarewsbinding.qos.aggretation.MinAverageAggregationFunction; +import es.us.isa.qosawarewsbinding.qos.aggretation.ProductoryAggregationFunction; +import es.us.isa.qosawarewsbinding.qos.aggretation.ProductoryPowAggregationFunction; +import es.us.isa.qosawarewsbinding.qos.aggretation.SumatoryAggregationFunction; +import es.us.isa.qosawarewsbinding.qos.aggretation.SumatoryPowAggregationFunction; +import es.us.isa.qosawarewsbinding.util.MyStringTokenizer; + +/** + * + * @author japarejo + */ +public class ProblemReaderAndWriter { + + public static QoSAwareWSCompositionProblem load(String fileName) + { + File f=new File(fileName); + return load(f); + } + public static void write(String FileName, QoSAwareWSCompositionProblem problem) + { + File f=new File(FileName); + write(f,problem); + } + + private static AbstractWebService getAbstractWebService(String serviceName, QoSAwareWSCompositionProblem result) { + AbstractWebService aws=null; + Set services=result.getStructure().getComponents(); + for(AbstractWebService candidate:services) + if(serviceName.equalsIgnoreCase(candidate.toString())) + aws=candidate; + return aws; + } + + private static AggregationFunction getAggregationFunction(String functionName) { + AggregationFunction result=null; + if(functionName.equalsIgnoreCase("MAX")) + result=MaxAggregationFunction.getInstance(); + else if(functionName.equalsIgnoreCase("MIN")) + result=MinAggregationFunction.getInstance(); + else if(functionName.equalsIgnoreCase("SUM")) + result=SumatoryAggregationFunction.getInstance(); + else if(functionName.equalsIgnoreCase("PRODUCT")) + result=ProductoryAggregationFunction.getInstance(); + else if(functionName.equalsIgnoreCase("POW")) + result=ProductoryPowAggregationFunction.getInstance(); + // New aggregation functions + else if(functionName.equalsIgnoreCase("SUMPOW")) + result=SumatoryPowAggregationFunction.getInstance(); + else if(functionName.equalsIgnoreCase("AVG")) + result=AverageAggregationFunction.getInstance(); + else if(functionName.equalsIgnoreCase("MAXAVG")) + result=MaxAverageAggregationFunction.getInstance(); + else if(functionName.equalsIgnoreCase("MINAVG")) + result=MinAverageAggregationFunction.getInstance(); + return result; + } + + private static Class getStructuralClass(String className) { + Class result=null; + if(className.equalsIgnoreCase("LOOP")) + result=Loop.class; + else if(className.equalsIgnoreCase("BRANCH")) + result=Branch.class; + else if(className.equalsIgnoreCase("SEQUENCE")) + result=Sequence.class; + else if(className.equalsIgnoreCase("FLOW")) + result=Flow.class; + return result; + } + + private static StructuralComponent loadAWS(String line, BufferedReader reader, List awsList) { + StructuralComponent component=null; + line=line.trim(); + if(!line.equals("")) + { + component=searchAWS(line,awsList); + } + return component; + } + + private static StructuralComponent loadBranch(String substring, BufferedReader reader, List awsList) throws IOException { + + StructuralComponent subComponent; + Branch result=new Branch(); + MyStringTokenizer strtok=null; + strtok=new MyStringTokenizer(substring,";",'(',')'); + List probabilities=new LinkedList(); + String value; + while(strtok.hasMoreTokens()) + { + value=strtok.nextToken(); + if(!value.equals(")[")) + probabilities.add(Double.valueOf(value)); + } + int index=0; + do{ + + strtok=new MyStringTokenizer(substring,",",'[',']'); + while(strtok.hasMoreTokens()) + { + subComponent=loadStructureComponent(strtok.nextToken(),reader,awsList); + if(subComponent!=null){ + result.addBranch(subComponent, probabilities.get(index)); + index++; + } + } + if(substring.length()==0 || !(substring.charAt(substring.length()-1)==']')) + substring=reader.readLine(); + }while(substring.length()==0 || !(substring.charAt(substring.length()-1)==']')); + return result; + } + + private static void loadConstraints(QoSAwareWSCompositionProblem result, BufferedReader reader) throws IOException { + String line=reader.readLine(); + while(line.charAt(0)=='%') + line=reader.readLine(); + int nConstraints=Integer.valueOf(line.trim()); + line=reader.readLine(); + if(line.charAt(0)=='%') + line=reader.readLine(); + String stroperator; + BinaryOperator operator; + String propertyName; + QoSProperty property; + String value; + while(line.charAt(0)!='%') + { + line=line.trim(); + MyStringTokenizer strtok=new MyStringTokenizer(line, "(),"); + stroperator=strtok.nextToken("("); + operator=BinaryOperator.valueOf(stroperator); + propertyName=strtok.nextToken(","); + property=result.getQosmodel().getQoSProperty(propertyName); + value=strtok.nextToken(")"); + result.getConstraints().add(new GlobalQoSWSCompositionConstraint(result, property, Double.valueOf(value), operator)); + line=reader.readLine(); + } + } + + private static StructuralComponent loadFlow(String substring, BufferedReader reader, List awsList) { + throw new UnsupportedOperationException("Not yet implemented"); + } + + private static StructuralComponent loadLoop(String substring, BufferedReader reader, List awsList) throws IOException { + String number=substring.substring(0,substring.lastIndexOf(")")); + Integer averageNumberOfIterations=Integer.valueOf(number); + StructuralComponent subComponent; + Loop result=new Loop(averageNumberOfIterations); + substring=substring.substring(substring.lastIndexOf(")")+2); + MyStringTokenizer strtok=null; + do{ + strtok=new MyStringTokenizer(substring,",",'[',']'); + while(strtok.hasMoreTokens()) + { + subComponent=loadStructureComponent(strtok.nextToken(),reader,awsList); + if(subComponent!=null) + result.getSubComponents().add(subComponent); + } + if(substring.length()==0 || !(substring.charAt(substring.length()-1)==']')) + substring=reader.readLine(); + }while(substring.length()==0 || !(substring.charAt(substring.length()-1)==']')); + return result; + } + + private static void loadMarket(QoSAwareWSCompositionProblem result, BufferedReader reader) throws IOException { + Map> market=result.getMarket(); + market.clear(); + String line=reader.readLine(); + while(line.charAt(0)=='%') + line=reader.readLine(); + line=reader.readLine(); + String serviceName=line.trim(); + String cwsName; + String propertyName; + String propertyValue; + AbstractWebService aws=null; + MyStringTokenizer strtok=null; + Set awsSet=null;; + ConcreteWebService cws=null; + while(line.charAt(0)!='%') + { + serviceName=line.trim(); + aws=getAbstractWebService(serviceName,result); + awsSet=new HashSet(); + line=reader.readLine(); + line=reader.readLine(); + while(line.charAt(0)!='-') + { + strtok=new MyStringTokenizer(line, "():,"); + cwsName=strtok.nextToken("("); + cws=new ConcreteWebService(cwsName, aws); + while(strtok.hasMoreTokens()) + { + propertyName=strtok.nextToken(":"); + if(!propertyName.equals(")")){ + propertyValue=strtok.nextToken(","); + try{ + cws.setQoSValue(result.getQosmodel().getQoSProperty(propertyName), Double.valueOf(propertyValue)); + }catch(Exception ex){ + System.out.println(ex); + System.out.println("ON LINE: "+line); + System.out.println("VALUE CAUSING ERROR:"+propertyValue); + } + } + } + awsSet.add(cws); + line=reader.readLine(); + } + market.put(aws, awsSet); + line=reader.readLine(); + } + } + + private static WSCompositionQoSModel loadQoSModel(BufferedReader reader) throws IOException { + WSCompositionQoSModel result=null; + String line=reader.readLine(); + while(line.length()==0 || line.charAt(0)=='%') + line=reader.readLine(); + line=line.trim(); + Set properties; + if(line.equals("QoSModel{")){ + properties=loadQoSModelProperties(reader); + result=new WSCompositionQoSModel(properties); + loadQoSModelAggregationFunctions(reader,result); + loadQoSModelWeights(reader,result); + line=reader.readLine(); + } + return result; + } + + private static void loadQoSModelAggregationFunctions(BufferedReader reader, WSCompositionQoSModel result) throws IOException { + String line=reader.readLine(); + line=line.trim(); + String name; + String function; + QoSProperty property; + String className; + Class myclass; + String functionName; + AggregationFunction aggregationf; + + if(line.equals("AggregationFunctions(")) + { + line=reader.readLine(); + line=line.trim(); + while((!line.equals(")"))) + { + if(line.charAt(line.length()-1)=='{') + { + name=line.substring(0, line.length()-1); + property=result.getQoSProperty(name); + line=reader.readLine(); + line=line.trim(); + while(!line.equals("}")) + { + MyStringTokenizer strtok=new MyStringTokenizer(line,":"); + className=strtok.nextToken(); + functionName=strtok.nextToken(); + myclass=getStructuralClass(className); + aggregationf=getAggregationFunction(functionName); + result.setAggregationFunction(property, myclass, aggregationf); + line=reader.readLine(); + line=line.trim(); + } + } + line=reader.readLine(); + line=line.trim(); + } + } + } + + private static Set loadQoSModelProperties(BufferedReader reader) throws IOException { + Set result=new HashSet(); + QoSProperty property=null; + String line=reader.readLine(); + line=line.trim(); + String name; + String type; + + if(line.equals("Properties{")) + { + line=reader.readLine(); + line=line.trim(); + while((!line.equals("}"))) + { + MyStringTokenizer strtok=new MyStringTokenizer(line, ":-"); // TODO Change the separator (problems in linux!) + if(strtok.hasMoreTokens()){ + name=strtok.nextToken(":"); + if(strtok.hasMoreTokens()){ + type=strtok.nextToken("-"); // TODO Change the separator (problems in linux!) + property=new QoSProperty(name,QoSPropertyType.valueOf(type)); + result.add(property); + } + } + line=reader.readLine(); + line=line.trim(); + } + } + return result; + } + + private static void loadQoSModelWeights(BufferedReader reader, WSCompositionQoSModel result) throws IOException { + String line=reader.readLine(); + line=line.trim(); + String name; + String value; + Map qosPropertiesWeights=new HashMap(); + if(line.equals("Weights(")) + { + line=reader.readLine(); + line=line.trim(); + while((!line.equals(")"))) + { + MyStringTokenizer strtok=new MyStringTokenizer(line,":"); + name=strtok.nextToken(); + value=strtok.nextToken(); + qosPropertiesWeights.put(result.getQoSProperty(name),Double.valueOf(value)); + line=reader.readLine(); + line=line.trim(); + } + } + result.setQosPropertiesWeights(qosPropertiesWeights); + } + + + private static StructuralComponent loadSequence(String substring, BufferedReader reader, List awsList) throws IOException { + Sequence result=new Sequence(); + StructuralComponent subComponent; + substring=substring.trim(); + + MyStringTokenizer strtok=null; + do + { + strtok=new MyStringTokenizer(substring,",",'[',']'); + while(strtok.hasMoreTokens()) + { + subComponent=loadStructureComponent(strtok.nextToken(),reader,awsList); + if(subComponent!=null) + result.getSubComponents().add(subComponent); + } + if(substring.length()==0 || !(substring.charAt(substring.length()-1)==']')) + substring=reader.readLine(); + }while(substring.length()==0 || !(substring.charAt(substring.length()-1)==']')); + return result; + } + + private static WSCompositionStructure loadStructure(BufferedReader reader) { + WSCompositionStructure result=null; + String line; + List awsList=new LinkedList(); + try { + line = reader.readLine(); + while(line.charAt(0)=='%') + line=reader.readLine(); + int nAbstractServices=Integer.parseInt(line); + for(int i=0;i awsList) throws IOException + { + StructuralComponent result=null; + line=line.trim(); + if(line.substring(0, Math.min(3, line.length())).equalsIgnoreCase("SEC")) + { + result=loadSequence(line.substring(4),reader,awsList); + }else if(line.substring(0,Math.min(4, line.length())).equalsIgnoreCase("LOOP")){ + result=loadLoop(line.substring(5),reader,awsList); + }else if(line.substring(0,Math.min(6, line.length())).equalsIgnoreCase("BRANCH")){ + result=loadBranch(line.substring(7),reader,awsList); + }else if(line.substring(0,Math.min(3, line.length())).equalsIgnoreCase("FLOW")){ + result=loadFlow(line.substring(5),reader,awsList); + }else{ + result=loadAWS(line,reader,awsList); + } + return result; + } + + private static void printConstraints(PrintWriter writer, List constraints) { + writer.println("%#======================= CONSTRAINTS =============================#"); + writer.println(constraints.size()); + writer.println("% ----------------------"); + for(WSCompositionConstraint constraint:constraints) + { + writer.println(constraint); + } + writer.println("% ----------------------"); + } + + private static void printHeader(PrintWriter writer, String fileName, QoSAwareWSCompositionProblem problem) { + writer.println("%#============================= HEADER ======================================#"); + writer.println("% This file contains an instance of the QoS-Aware Web Services Composition Problem "); + writer.println("% FILE: "+fileName); + writer.println("% Created by: Jos Antonio Parejo Mestre"); + writer.println("% "+new Date()); + writer.println("% ----------------------"); + writer.println("% Problem Statistics: "); + writer.println("% ----------------------"); + writer.println("% Number of activities: "+problem.getStructure().numberOfActivities()); + int nServices=0; + Collection> values=problem.getMarket().values(); + for(Set services:values) + nServices+=services.size(); + writer.println("% Number of Candidate Services: "+nServices); + writer.println("% Number of Constraints: "+problem.getConstraints().size()); + } + + private static void printMarket(PrintWriter writer, Map> market,WSCompositionQoSModel qosModel) { + writer.println("%#======================= CANDIDATE SERVICES =============================#"); + writer.println("------------------------"); + Set cwservices; + for(AbstractWebService aws:market.keySet()) + { + writer.println(aws); + writer.println("------------------------"); + cwservices=market.get(aws); + for(ConcreteWebService cws:cwservices) + { + writer.print(cws+"("); + for (QoSProperty property : qosModel.getQosProperties()) { + writer.print(property.getName() + ":" + cws.getQoSValue(property) + ","); + } + writer.println(")"); + } + writer.println("------------------------"); + } + } + + private static void printQoSModel(PrintWriter writer, WSCompositionQoSModel qosmodel) { + writer.println("%#======================= QOS MODEL =============================#"); + writer.println(qosmodel); + } + + private static void printStructure(PrintWriter writer, WSCompositionStructure structure) { + writer.println("%#======================= COMPOSITION STRUCTURE =============================#"); + writer.println("% Abstract Services:"); + writer.println("%----------------------"); + writer.println(structure.getComponents().size()); + for(AbstractWebService aws:structure.getComponents()) + writer.println(aws); + writer.println("% CompositionStructure:"); + writer.println("%----------------------"); + writer.println(structure.getStructure().toStructuralString("")); + } + + public static QoSAwareWSCompositionProblem load(File f) { + QoSAwareWSCompositionProblem result=null; + WSCompositionStructure structure=null; + WSCompositionQoSModel qosmodel=null; + Map> market=null; + List constraints; + BufferedReader reader; + String line; + try { + reader = new BufferedReader(new FileReader(f)); + line=reader.readLine(); + line=reader.readLine(); + if(line.equals("% This file contains an instance of the QoS-Aware Web Services Composition Problem ")) + { + // We skip the header: + while(line.charAt(0)=='%' && line.charAt(1)==' ') + line=reader.readLine(); + structure=loadStructure(reader); + qosmodel=loadQoSModel(reader); + result=new QoSAwareWSCompositionProblem(structure, qosmodel); + loadMarket(result,reader); + loadConstraints(result,reader); + } + } catch (FileNotFoundException ex) { + Logger.getLogger(ProblemReaderAndWriter.class.getName()).log(Level.SEVERE, null, ex); + }catch (IOException ex) { + Logger.getLogger(ProblemReaderAndWriter.class.getName()).log(Level.SEVERE, null, ex); + } + if(result!=null) + result.scale(); + return result; + } + + private static StructuralComponent searchAWS(String line, List awsList) { + StructuralComponent result=null; + for(AbstractWebService aws:awsList) + { + if(aws.toString().equals(line)) + result=aws; + } + return result; + } + + private static void write(File f, QoSAwareWSCompositionProblem problem) { + PrintWriter writer; + try { + writer = new PrintWriter(new BufferedOutputStream(new FileOutputStream(f))); + printHeader(writer,f.getName(),problem); + printStructure(writer,problem.getStructure()); + printQoSModel(writer,problem.getQosmodel()); + printMarket(writer,problem.getMarket(),problem.getQosmodel()); + printConstraints(writer,problem.getConstraints()); + writer.close(); + } catch (FileNotFoundException ex) { + Logger.getLogger(ProblemReaderAndWriter.class.getName()).log(Level.SEVERE, null, ex); + } + } + + + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/QoSAwareWSCompositionProblem.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/QoSAwareWSCompositionProblem.java new file mode 100644 index 0000000..87b3bce --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/QoSAwareWSCompositionProblem.java @@ -0,0 +1,333 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package es.us.isa.qosawarewsbinding.problem; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import es.us.isa.qosawarewsbinding.AbstractWebService; +import es.us.isa.qosawarewsbinding.ConcreteWebService; +import es.us.isa.qosawarewsbinding.WSCompositionStructure; +import es.us.isa.qosawarewsbinding.qos.QoSProperty; +import es.us.isa.qosawarewsbinding.qos.QoSPropertyType; +import es.us.isa.qosawarewsbinding.solution.QoSAwareWSCompositionSolution; +import es.us.isa.qosawarewsbinding.solution.Solution; +import es.us.isa.qosawarewsbinding.solution.vector.QoSAwareWSCompositionVectorSolution; + +/** + * + * @author japarejo + */ +public class QoSAwareWSCompositionProblem extends FeasibilityAwareProblem implements Serializable { + + private WSCompositionStructure structure; + private Map> market; + private WSCompositionQoSModel qosmodel; + private List constraints; + private Set expaths; + private static ExecutionPathsBuilder expathBuilder = null; + public boolean scaled; + + public QoSAwareWSCompositionProblem(WSCompositionStructure structure, Map> market, WSCompositionQoSModel qosmodel, List constraints) { + this.structure = structure; + this.market = market; + this.qosmodel = qosmodel; + this.constraints = constraints; + this.expaths = null; + this.scaled = false; + } + + public QoSAwareWSCompositionProblem(WSCompositionStructure structure, WSCompositionQoSModel qosmodel) { + this(structure, new HashMap>(), qosmodel, new LinkedList()); + } + + public int numberOfCandidates(AbstractWebService aws) { + return getMarket().get(aws).size(); + } + + public String getDescription() { + throw new UnsupportedOperationException("Not supported yet."); + } + + public WSCompositionStructure getStructure() { + return structure; + } + + public Map> getMarket() { + return market; + } + + public WSCompositionQoSModel getQosmodel() { + return qosmodel; + } + + public List getConstraints() { + return constraints; + } + + public String toString() { + StringBuffer buffer = new StringBuffer("=== QoS-aware Web Service Composition Problem Instance ===\n"); + buffer.append(getStructure().getStructure().toStructuralString("")); + buffer.append(toStringMarket()); + buffer.append(getQosmodel()); + buffer.append(getConstraints()); + buffer.append("==========================================================\n"); + return buffer.toString(); + + } + + private double scale(double value, double Qmax, double Qmin, QoSProperty property) { + double result = 0; + if (Qmax != Qmin) { + if (property.getType() == QoSPropertyType.POSITIVE) { + result = (value - Qmin) / (Qmax - Qmin); + } else { + result = (Qmax - value) / (Qmax - Qmin); + } + } else { + result = 1.0; + } + return result; + } + + private String toStringMarket() { + + StringBuffer buffer = new StringBuffer("--- MARKET OF SERVICES ---"); + for (AbstractWebService aws : market.keySet()) { + buffer.append("\nAWS" + aws.toString() + ":"); + for (ConcreteWebService cws : market.get(aws)) { + buffer.append(" S" + cws.getName() + "("); + for (QoSProperty property : getQosmodel().getQosProperties()) { + buffer.append(property.getName() + ":" + cws.getQoSValue(property) + ","); + } + buffer.append(")"); + } + } + buffer.append("\n--------------------------\n"); + return buffer.toString(); + } + + public double feasibilityDistance(Solution sol) { + double value = 0; + for (WSCompositionConstraint constraint : getConstraints()) { + value = constraint.meetingDistance((QoSAwareWSCompositionSolution) sol); + } + return value; + } + + protected double computeFitness(Solution sol) { + double feasibilityDistance = Math.min(1, feasibilityDistance(sol)); + double result = feasibilityFreeFitness(sol); + if (getPenalizator() != null) { + result = getPenalizator().penalize(result, (1.0 - feasibilityDistance)); + } + return 1.0 - result; + } + + public double feasibilityFreeFitness(Solution sol) { + double result = 0; + if (!scaled) { + scale(); + } + result = getQosmodel().evaluate((QoSAwareWSCompositionSolution) sol, getStructure()); + result = result / numberOfExecutedTasks(); + return result; + } + + public double candidatesPerService() { + int totalNumberOfCandidates = 0; + for (AbstractWebService aws : getMarket().keySet()) { + totalNumberOfCandidates += getMarket().get(aws).size(); + } + return ((double) (totalNumberOfCandidates)) / ((double) (getMarket().keySet().size())); + } + + public Set getExecutionPaths() { + if (getExpaths() == null) { + if (expathBuilder != null) { + setExpaths(expathBuilder.buildPaths(this)); + } else { + expathBuilder = new LoopUnfoldingExecutionPathsBuilder(); + setExpaths(expathBuilder.buildPaths(this)); + } + } + return getExpaths(); + } + + public boolean isScaled() { + return scaled; + } + + public void scale() { + for (QoSProperty property : getQosmodel().getQosProperties()) { + scale(property); + } + scaled = true; + //System.out.println("Problem Reescaled!! Current State:"); + //System.out.println(this); + } + + private void scale(QoSProperty property) { + double Qmax = max(property); + double Qmin = min(property); + double value = 0; + for (AbstractWebService aws : getMarket().keySet()) { + for (ConcreteWebService cws : getMarket().get(aws)) { + Double objVal = (Double) cws.getQoSValue(property); + if (objVal != null) { + value = objVal; + value = scale(value, Qmax, Qmin, property); + cws.setQoSValue(property, value); + } + } + } + + for (WSCompositionConstraint constraint : constraints) { + if (constraint instanceof GlobalQoSWSCompositionConstraint) { + if (((GlobalQoSWSCompositionConstraint) constraint).getProperty().equals(property)) { + value = ((GlobalQoSWSCompositionConstraint) constraint).getValue(); + value = scale(value, Qmax, Qmin, property); + if (value < 0) { + value = 1 - value; + } + ((GlobalQoSWSCompositionConstraint) constraint).setValue(value); + } + } + } + + } + + private double max(QoSProperty property) { + double result = -Double.MAX_VALUE; + boolean found = false; + for (AbstractWebService aws : getMarket().keySet()) { + for (ConcreteWebService cws : market.get(aws)) { + Double candidate = (Double) cws.getQoSValue(property); + if (candidate != null) { + if (candidate > result) { + result = candidate; + } + found = true; + } + } + } + return found ? result : 1.0; // Default max if no values + } + + private double min(QoSProperty property) { + double result = Double.MAX_VALUE; + boolean found = false; + for (AbstractWebService aws : market.keySet()) { + for (ConcreteWebService cws : market.get(aws)) { + Double candidate = (Double) cws.getQoSValue(property); + if (candidate != null) { + if (candidate < result) { + result = candidate; + } + found = true; + } + } + } + return found ? result : 0.0; // Default min if no values + } + + public void setStructure(WSCompositionStructure structure) { + this.structure = structure; + } + + public void setQosmodel(WSCompositionQoSModel qosmodel) { + this.qosmodel = qosmodel; + } + + public void setConstraints(List constraints) { + this.constraints = constraints; + } + + public Set getExpaths() { + return expaths; + } + + public void setExpaths(Set expaths) { + this.expaths = expaths; + } + + public double numberOfExecutedTasks() { + return structure.numberOfExecutedTasks(); + } + + public String getProblemType() { + return "QoS-awareCWSBinding"; + } + + public Double bestValue(QoSProperty property) { + QoSAwareWSCompositionSolution solution = computeBestSolution(property); + return qosmodel.evaluate(solution, property, structure); + } + + public Double worstValue(QoSProperty property) { + QoSAwareWSCompositionSolution solution = computeWorstSolution(property); + return qosmodel.evaluate(solution, property, structure); + } + + public QoSAwareWSCompositionSolution computeBestSolution(QoSProperty property) { + QoSAwareWSCompositionVectorSolution result = new QoSAwareWSCompositionVectorSolution(this); + ConcreteWebService bestCandidate = null; + + for (AbstractWebService aws : market.keySet()) { + for (ConcreteWebService cws : market.get(aws)) { + if (bestCandidate == null) { + bestCandidate = cws; + } else { + if (property.getType() == QoSPropertyType.POSITIVE) { + if (((Double) cws.getQoSValue(property)) > ((Double) bestCandidate.getQoSValue(property))) { + bestCandidate = cws; + } + } else { + if (((Double) cws.getQoSValue(property)) < ((Double) bestCandidate.getQoSValue(property))) { + bestCandidate = cws; + } + } + } + } + result.setSelectedService(aws, bestCandidate); + bestCandidate = null; + } + return result; + } + + public QoSAwareWSCompositionSolution computeWorstSolution(QoSProperty property) { + QoSAwareWSCompositionVectorSolution result = new QoSAwareWSCompositionVectorSolution(this); + ConcreteWebService worstCandidate = null; + System.out.println("Search worst solution..."); + for (AbstractWebService aws : market.keySet()) { + for (ConcreteWebService cws : market.get(aws)) { + if (worstCandidate == null) { + worstCandidate = cws; + } else { + + System.out.println("type: " + property.getType() + " worst: " + ((Double) worstCandidate.getQoSValue(property)) + " cws: " + ((Double) cws.getQoSValue(property))); + + if (property.getType() == QoSPropertyType.POSITIVE) { + if (((Double) cws.getQoSValue(property)) < ((Double) worstCandidate.getQoSValue(property))) { + worstCandidate = cws; + } + } else { + if (((Double) cws.getQoSValue(property)) > ((Double) worstCandidate.getQoSValue(property))) { + worstCandidate = cws; + } + } + System.out.println("Updated worst: " + ((Double) worstCandidate.getQoSValue(property))); + } + } + result.setSelectedService(aws, worstCandidate); + worstCandidate = null; + } + return result; + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/ServicesDespendenceWSCompositionConstraint.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/ServicesDespendenceWSCompositionConstraint.java new file mode 100644 index 0000000..b952034 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/ServicesDespendenceWSCompositionConstraint.java @@ -0,0 +1,47 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.problem; + +import java.io.Serializable; +import java.util.Map; + +import es.us.isa.qosawarewsbinding.AbstractWebService; +import es.us.isa.qosawarewsbinding.ConcreteWebService; +import es.us.isa.qosawarewsbinding.solution.QoSAwareWSCompositionSolution; + +/** + * + * @author japarejo + */ +public class ServicesDespendenceWSCompositionConstraint extends WSCompositionConstraint implements Serializable{ + + public Map dependences; + + public ServicesDespendenceWSCompositionConstraint(QoSAwareWSCompositionProblem problem, Map dependences) + { + super(problem); + this.dependences=dependences; + } + + @Override + public boolean meets(QoSAwareWSCompositionSolution solution) { + return meetingDistance(solution)==0; + } + + @Override + public double meetingDistance(QoSAwareWSCompositionSolution solution) { + double distance=0; + int present=0; + for(AbstractWebService aws:dependences.keySet()) + { + if(solution.isUsing(aws, dependences.get(aws))) + present++; + } + distance=((double)present)/((double)dependences.keySet().size()); + return distance; + } + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/UnfeasibilityPenalizator.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/UnfeasibilityPenalizator.java new file mode 100644 index 0000000..0c8a5d2 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/UnfeasibilityPenalizator.java @@ -0,0 +1,7 @@ +package es.us.isa.qosawarewsbinding.problem; + +public interface UnfeasibilityPenalizator { + + public double penalize(double fitness,double feasibilityDistance); + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/WSCompositionConstraint.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/WSCompositionConstraint.java new file mode 100644 index 0000000..dbf7350 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/WSCompositionConstraint.java @@ -0,0 +1,29 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.problem; + +import es.us.isa.qosawarewsbinding.solution.QoSAwareWSCompositionSolution; + +/** + * + * @author japarejo + */ +public abstract class WSCompositionConstraint implements Constraint { + + protected QoSAwareWSCompositionProblem problem; + + public WSCompositionConstraint(QoSAwareWSCompositionProblem problem) + { + this.problem=problem; + } + + + public abstract boolean meets(QoSAwareWSCompositionSolution solution); + + public abstract double meetingDistance(QoSAwareWSCompositionSolution solution); + + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/WSCompositionQoSModel.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/WSCompositionQoSModel.java new file mode 100644 index 0000000..d226994 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/WSCompositionQoSModel.java @@ -0,0 +1,168 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.problem; + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + + + +import es.us.isa.qosawarewsbinding.AbstractWebService; +import es.us.isa.qosawarewsbinding.CompositeStructuralComponent; +import es.us.isa.qosawarewsbinding.StructuralComponent; +import es.us.isa.qosawarewsbinding.WSCompositionStructure; +import es.us.isa.qosawarewsbinding.qos.QoSProperty; +import es.us.isa.qosawarewsbinding.qos.aggretation.AggregationFunction; +import es.us.isa.qosawarewsbinding.solution.QoSAwareWSCompositionSolution; + +/** + * + * @author japarejo + */ +public class WSCompositionQoSModel implements Serializable{ + private Set qosProperties; + Map> aggregationFunctions; + private Map qosPropertiesWeights; + + public WSCompositionQoSModel(Set qosProperties) + { + this.qosProperties=qosProperties; + aggregationFunctions=new HashMap>(); + qosPropertiesWeights=new HashMap(); + Double value=1.0/((double)qosProperties.size()); + for(QoSProperty property:qosProperties) + qosPropertiesWeights.put(property, value); + } + + public WSCompositionQoSModel(Map> aggregationFunctions) + { + this.qosProperties=aggregationFunctions.keySet(); + this.aggregationFunctions=aggregationFunctions; + Double value=1.0/((double)this.qosProperties.size()); + for(QoSProperty property:qosProperties) + qosPropertiesWeights.put(property, value); + } + + public AggregationFunction getAggregationFunction(QoSProperty property,Class pclass) + { + AggregationFunction result=null; + Map functions=aggregationFunctions.get(property); + if(functions!=null) + result=functions.get(pclass); + return result; + } + + public void setAggregationFunction(QoSProperty property,Class pclass, AggregationFunction aggregationFunction) + { + Map functions=aggregationFunctions.get(property); + if(functions==null) + { + functions=new HashMap(); + aggregationFunctions.put(property, functions); + } + functions.put(pclass, aggregationFunction); + } + + public Double getQoSPropertyWeight(QoSProperty property) + { + return qosPropertiesWeights.get(property); + } + + public Set getQosProperties() { + return qosProperties; + } + + public void setQosPropertiesWeights(Map qosPropertiesWeights) { + this.qosPropertiesWeights = qosPropertiesWeights; + } + + public QoSProperty getQoSProperty(String name) + { + QoSProperty result=null; + for(QoSProperty property:qosProperties) + if(property.getName().equals(name)) + result=property; + return result; + } + + public Double evaluate(QoSAwareWSCompositionSolution solution,WSCompositionStructure compositionStructure) + { + double result=0; + double qosPropertyAportation; + for(QoSProperty property:qosProperties){ + qosPropertyAportation=evaluate(solution,property,compositionStructure); + result+=qosPropertiesWeights.get(property).doubleValue()*qosPropertyAportation; + } + return result; + } + + public Double evaluate(QoSAwareWSCompositionSolution solution, QoSProperty property,WSCompositionStructure compositionStructure) + { + return evaluate(solution,property,compositionStructure.getStructure()); + } + + public Double evaluate(QoSAwareWSCompositionSolution solution, QoSProperty property, StructuralComponent component) + { + Double result=null; + if(component instanceof AbstractWebService) + result=(Double)solution.getSelectedService((AbstractWebService)component).getQoSValue(property); + else{ + AggregationFunction aggregationFunction=getAggregationFunction(property,component.getClass()); + List partialResults=new LinkedList(); + List ponderations=new LinkedList(); + CompositeStructuralComponent compositeComponent=(CompositeStructuralComponent)component; + Collection subcomponents=compositeComponent.getSubComponents(); + for(StructuralComponent subcomponent:subcomponents){ + if(subcomponent!=null && !subcomponent.isEmpty()){ + partialResults.add(evaluate(solution,property,subcomponent)); + ponderations.add(compositeComponent.getPonderation(subcomponent)); + } + } + result=aggregationFunction.aggregation(partialResults,ponderations); + } + return result; + } + + @Override + public String toString() + { + StringBuffer buffer=new StringBuffer(); + Map functionsPerProperty; + String newline = System.getProperty("line.separator"); + buffer.append("QoSModel{"+newline); + //buffer.append(" "+qosProperties+newline); + buffer.append(" "+"Properties{"+newline); + for(QoSProperty property:qosProperties) + buffer.append(" "+property.toString()+newline); + buffer.append(" }"+newline); + buffer.append(" AggregationFunctions("+newline); + for(QoSProperty property:aggregationFunctions.keySet()){ + buffer.append(" "+property.getName()+"{"+newline); + functionsPerProperty=aggregationFunctions.get(property); + for(Class structureClass:functionsPerProperty.keySet()) + { + buffer.append(" "+structureClass.getSimpleName()+":"+functionsPerProperty.get(structureClass)+newline); + } + buffer.append(" }"+newline); + } + buffer.append(" )"+newline); + buffer.append(" "+"Weights("+newline); + for(QoSProperty property:qosPropertiesWeights.keySet()) + { + buffer.append(" "+property.getName()+":"+qosPropertiesWeights.get(property).doubleValue()+newline); + } + buffer.append(" )"+newline); + buffer.append("}"); + return buffer.toString(); + + } + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/generator/AbstractProblemGenerator.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/generator/AbstractProblemGenerator.java new file mode 100644 index 0000000..ec8f217 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/generator/AbstractProblemGenerator.java @@ -0,0 +1,24 @@ +package es.us.isa.qosawarewsbinding.problem.generator; + +import es.us.isa.qosawarewsbinding.problem.Problem; +import es.us.isa.qosawarewsbinding.problem.model.ProblemModel; + +public abstract class AbstractProblemGenerator implements ProblemGenerator { + + private ProblemModel problemModel; + + public AbstractProblemGenerator(ProblemModel pmodel) + { + this.problemModel=pmodel; + } + + public ProblemModel getProblemModel() { + return problemModel; + } + + public void setProblemModel(ProblemModel problemModel) { + this.problemModel = problemModel; + } + + +} \ No newline at end of file diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/generator/ProblemGenerator.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/generator/ProblemGenerator.java new file mode 100644 index 0000000..01461bc --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/generator/ProblemGenerator.java @@ -0,0 +1,7 @@ +package es.us.isa.qosawarewsbinding.problem.generator; + +import es.us.isa.qosawarewsbinding.problem.Problem; + +public interface ProblemGenerator { + public X generate(); +} \ No newline at end of file diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/generator/QoSAwareWSCompositionProblemGenerator.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/generator/QoSAwareWSCompositionProblemGenerator.java new file mode 100644 index 0000000..2b37726 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/generator/QoSAwareWSCompositionProblemGenerator.java @@ -0,0 +1,381 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.problem.generator; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import es.us.isa.qosawarewsbinding.AbstractWebService; +import es.us.isa.qosawarewsbinding.Branch; +import es.us.isa.qosawarewsbinding.CompositeStructuralComponent; +import es.us.isa.qosawarewsbinding.ConcreteWebService; +import es.us.isa.qosawarewsbinding.Flow; +import es.us.isa.qosawarewsbinding.Loop; +import es.us.isa.qosawarewsbinding.Sequence; +import es.us.isa.qosawarewsbinding.StructuralComponent; +import es.us.isa.qosawarewsbinding.WSCompositionStructure; +import es.us.isa.qosawarewsbinding.problem.BinaryOperator; +import es.us.isa.qosawarewsbinding.problem.GlobalQoSWSCompositionConstraint; +import es.us.isa.qosawarewsbinding.problem.QoSAwareWSCompositionProblem; +import es.us.isa.qosawarewsbinding.problem.WSCompositionConstraint; +import es.us.isa.qosawarewsbinding.problem.WSCompositionQoSModel; +import es.us.isa.qosawarewsbinding.problem.model.GlobalQoSConstraintsModel; +import es.us.isa.qosawarewsbinding.problem.model.QoSAwareWSCompositionProblemConstraintsModel; +import es.us.isa.qosawarewsbinding.problem.model.QoSAwareWSCompositionProblemModel; +import es.us.isa.qosawarewsbinding.problem.model.QoSAwareWSCompositionProblemServicesMarketModel; +import es.us.isa.qosawarewsbinding.problem.model.QoSAwareWSCompositionProblemStructuralModel; +import es.us.isa.qosawarewsbinding.qos.QoSProperty; +import es.us.isa.qosawarewsbinding.qos.QoSPropertyType; +import es.us.isa.qosawarewsbinding.qos.aggretation.MaxAggregationFunction; +import es.us.isa.qosawarewsbinding.qos.aggretation.MinAggregationFunction; +import es.us.isa.qosawarewsbinding.qos.aggretation.ProductoryAggregationFunction; +import es.us.isa.qosawarewsbinding.qos.aggretation.ProductoryPowAggregationFunction; +import es.us.isa.qosawarewsbinding.qos.aggretation.SumatoryAggregationFunction; +import es.us.isa.qosawarewsbinding.solution.vector.QoSAwareWSCompositionVectorSolution; +import es.us.isa.qosawarewsbinding.util.BoundedDomain; +import es.us.isa.qosawarewsbinding.util.DistributionFunction; + +/** + * + * @author japarejo + */ +public class QoSAwareWSCompositionProblemGenerator extends AbstractProblemGenerator{ + + public QoSAwareWSCompositionProblemGenerator(QoSAwareWSCompositionProblemModel model) + { + super(model); + } + + + public QoSAwareWSCompositionProblem generate() + { + WSCompositionStructure structure=generateStructure(); + WSCompositionQoSModel qosModel=generateQoSModel(); + Map> market=generateMarket(structure,qosModel); + List constraints=generateConstraints(market,qosModel); + QoSAwareWSCompositionProblem problem=new QoSAwareWSCompositionProblem(structure,market,qosModel,constraints); + generateGlobalConstraints(constraints ,market,qosModel,problem); + generateAdditionalConstraints(constraints,market,qosModel,problem); + return problem; + } + + public double computeMax(QoSProperty property, Map> market,QoSAwareWSCompositionProblem problem) { + double result=0; + double localExtremeValue; + Double value; + ConcreteWebService cwsSelected=null; + QoSAwareWSCompositionVectorSolution solution=new QoSAwareWSCompositionVectorSolution(problem); + for(AbstractWebService aws:market.keySet()) + { + localExtremeValue=Double.MIN_VALUE; + cwsSelected=null; + for(ConcreteWebService cws:market.get(aws)) + { + value=(Double)cws.getQoSValue(property); + if(value>localExtremeValue || cwsSelected==null){ + localExtremeValue=value; + cwsSelected=cws; + } + } + solution.setSelectedService(aws, cwsSelected); + } + result=problem.getQosmodel().evaluate(solution, property, problem.getStructure().getStructure()); + return result; + } + + public double computeMin(QoSProperty property, Map> market,QoSAwareWSCompositionProblem problem) { + double result=0; + double localExtremeValue; + Double value; + ConcreteWebService cwsSelected=null; + QoSAwareWSCompositionVectorSolution solution=new QoSAwareWSCompositionVectorSolution(problem); + for(AbstractWebService aws:market.keySet()) + { + localExtremeValue=Double.MAX_VALUE; + cwsSelected=null; + for(ConcreteWebService cws:market.get(aws)) + { + value=(Double)cws.getQoSValue(property); + if(value generateConstraints(Map> market, WSCompositionQoSModel qosModel) { + List result=new LinkedList(); + + return result; + } + + public void generateGlobalConstraints(List result, Map> market, WSCompositionQoSModel qosModel,QoSAwareWSCompositionProblem problem) { + QoSAwareWSCompositionProblemModel problemModel=(QoSAwareWSCompositionProblemModel)getProblemModel(); + QoSAwareWSCompositionProblemConstraintsModel constraintsModel=problemModel.getConstraintsModel(); + GlobalQoSConstraintsModel globalConstraintsModel=constraintsModel.getGlobalConstraintsModel(); + int numberOfConstraints=globalConstraintsModel.getNumberOfConstraints(); + double probability=(double)numberOfConstraints/(double)qosModel.getQosProperties().size(); + double min; + double max; + double value; + double percentage; + BinaryOperator operator; + for(QoSProperty property:qosModel.getQosProperties()) + { + if(Math.random() result, Map> market, WSCompositionQoSModel qosModel,QoSAwareWSCompositionProblem problem) { + // TODO: Implement the generation of additional constraints. + } + private Map> generateMarket(WSCompositionStructure structure, WSCompositionQoSModel qosModel) + { + QoSAwareWSCompositionProblemModel problemModel=(QoSAwareWSCompositionProblemModel)getProblemModel(); + QoSAwareWSCompositionProblemServicesMarketModel marketModel=problemModel.getMarketModel(); + return generateMarket(structure,qosModel,marketModel); + } + public Map> generateMarket(WSCompositionStructure structure, WSCompositionQoSModel qosModel,QoSAwareWSCompositionProblemServicesMarketModel marketModel) { + Map> result=new HashMap>(); + Set setCWS=null; + for(AbstractWebService aws:structure.getComponents()) + { + setCWS=generateSubMarket(aws,qosModel,marketModel); + result.put(aws, setCWS); + } + return result; + } + + + + public WSCompositionQoSModel generateQoSModel() { + Set qosProperties=new HashSet(); + QoSProperty cost=new QoSProperty("Cost",new BoundedDomain(0.0,1.0),QoSPropertyType.NEGATIVE); + QoSProperty execTime=new QoSProperty("ExecTime",new BoundedDomain(0.0,1.0),QoSPropertyType.NEGATIVE); + QoSProperty reliability=new QoSProperty("Reliability",new BoundedDomain(0.0,1.0),QoSPropertyType.POSITIVE); + QoSProperty avaliability=new QoSProperty("Availability",new BoundedDomain(0.0,1.0),QoSPropertyType.POSITIVE); + QoSProperty security=new QoSProperty("Security", new BoundedDomain(0.0,1.0),QoSPropertyType.POSITIVE); + qosProperties.add(cost); + qosProperties.add(execTime); + qosProperties.add(reliability); + qosProperties.add(avaliability); + qosProperties.add(security); + WSCompositionQoSModel qosmodel=new WSCompositionQoSModel(qosProperties); + // Weights: + Map qosWeights=new HashMap(); + qosWeights.put(cost, 0.3); + qosWeights.put(execTime, 0.3); + qosWeights.put(reliability, 0.1); + qosWeights.put(avaliability, 0.1); + qosWeights.put(security, 0.2); + qosmodel.setQosPropertiesWeights(qosWeights); + // Aggregation Functions: + // For Cost: + qosmodel.setAggregationFunction(cost, Sequence.class, SumatoryAggregationFunction.getInstance()); + qosmodel.setAggregationFunction(cost, Loop.class, SumatoryAggregationFunction.getInstance()); + qosmodel.setAggregationFunction(cost, Branch.class, SumatoryAggregationFunction.getInstance()); + qosmodel.setAggregationFunction(cost, Flow.class, SumatoryAggregationFunction.getInstance()); + // For ExecTime: + qosmodel.setAggregationFunction(execTime, Sequence.class, SumatoryAggregationFunction.getInstance()); + qosmodel.setAggregationFunction(execTime, Loop.class, SumatoryAggregationFunction.getInstance()); + qosmodel.setAggregationFunction(execTime, Branch.class, SumatoryAggregationFunction.getInstance()); + qosmodel.setAggregationFunction(execTime, Flow.class, MaxAggregationFunction.getInstance()); + // For Reliability: + qosmodel.setAggregationFunction(reliability, Sequence.class, ProductoryAggregationFunction.getInstance()); + qosmodel.setAggregationFunction(reliability, Loop.class, ProductoryPowAggregationFunction.getInstance()); + qosmodel.setAggregationFunction(reliability, Branch.class, SumatoryAggregationFunction.getInstance()); + qosmodel.setAggregationFunction(reliability, Flow.class, ProductoryAggregationFunction.getInstance()); + // For Availability: + qosmodel.setAggregationFunction(avaliability, Sequence.class, ProductoryAggregationFunction.getInstance()); + qosmodel.setAggregationFunction(avaliability, Loop.class, ProductoryPowAggregationFunction.getInstance()); + qosmodel.setAggregationFunction(avaliability, Branch.class, SumatoryAggregationFunction.getInstance()); + qosmodel.setAggregationFunction(avaliability, Flow.class, ProductoryAggregationFunction.getInstance()); + // For Security: + qosmodel.setAggregationFunction(security, Sequence.class, MinAggregationFunction.getInstance()); + qosmodel.setAggregationFunction(security, Loop.class, MinAggregationFunction.getInstance()); + qosmodel.setAggregationFunction(security, Branch.class, SumatoryAggregationFunction.getInstance()); + qosmodel.setAggregationFunction(security, Flow.class, MinAggregationFunction.getInstance()); + return qosmodel; + } + + public CompositeStructuralComponent generateStructuralComponent(QoSAwareWSCompositionProblemStructuralModel structuralModel) { + CompositeStructuralComponent result=null; + // This method must create the structural components associated with the control flow, + // asigning their ponderation (number of iterations in the case of loops, and execution probability for + // each branch in the case of IF-ELSEs. + // Moreover in the case of IF-ELSEs, this method will create a sequence as the structural componen associated with + // each branch + double random=Math.random(); + if(random<(double)(structuralModel.getPercentageLoops()/100.0)){ + int numberOfIterations=structuralModel.getIterationsPerLoop().getValue(); + result=new Loop(numberOfIterations); + }else{ + random=Math.random(); + Branch branch=new Branch(); + CompositeStructuralComponent branch1=new Sequence(); + CompositeStructuralComponent branch2=new Sequence(); + branch.addBranch(branch1, random); + branch.addBranch(branch2, (1.0-random)); + result=branch; + } + return result; + } + + private WSCompositionStructure generateStructure() + { + QoSAwareWSCompositionProblemModel problemModel=(QoSAwareWSCompositionProblemModel)getProblemModel(); + QoSAwareWSCompositionProblemStructuralModel structuralModel=problemModel.getStructuralModel(); + return generateStructure(structuralModel); + } + + public WSCompositionStructure generateStructure(QoSAwareWSCompositionProblemStructuralModel structuralModel) { + WSCompositionStructure structure=null; + int nAbstractWebServices=(100-structuralModel.getPercentageOfControlFlowActivities())*structuralModel.getNumberOfActivities()/100; + AbstractWebService []services=new AbstractWebService[nAbstractWebServices]; + // We create the abstract web services: + for(int i=0;i structuralComponents=new LinkedList(); + int nControlFlowActivities=structuralModel.getPercentageOfControlFlowActivities()*structuralModel.getNumberOfActivities()/100; + for(int i=0;i candidatesToInsertion=new LinkedList(); + candidatesToInsertion.add(root); + // We create another list to store the candidate composite components to the insertion of others that has no subcomponentes: + List emptyCandidatesToInsertion=new LinkedList(); + // We distribute this structures in a control graph (in this case in a control tree) + // having in to account the constraint on the nesting level: + + // We Create a structure to store the nesting level of each component: + Map nestingLevel=new HashMap(); + nestingLevel.put(root, 0); + // We distribute these components: + StructuralComponent currentComponentToDistribute; + CompositeStructuralComponent componentWhereWeNest; + while(!structuralComponents.isEmpty()) + { + currentComponentToDistribute=(StructuralComponent)getRandomElement(structuralComponents); + componentWhereWeNest=(CompositeStructuralComponent)getRandomElement(candidatesToInsertion); + // We impose the maximum nesting level: + while(nestingLevel.get(componentWhereWeNest)>=structuralModel.getMaxNestingLevel()) + componentWhereWeNest=(CompositeStructuralComponent)getRandomElement(candidatesToInsertion); + // We insert the structural component in a randomly selected position: + insertRandomly(currentComponentToDistribute, componentWhereWeNest); + // We updae the nesting level of each component: + updateNestingLevel(currentComponentToDistribute, componentWhereWeNest,nestingLevel,candidatesToInsertion,emptyCandidatesToInsertion); + structuralComponents.remove(currentComponentToDistribute); + } + // Once the control structure is created, we distribute the AbstractWebService invocations: + for(AbstractWebService service:services) + { + if(emptyCandidatesToInsertion.isEmpty()){ + componentWhereWeNest=(CompositeStructuralComponent)getRandomElement(candidatesToInsertion); + insertRandomly(service,componentWhereWeNest); + }else{ + componentWhereWeNest=(CompositeStructuralComponent)getRandomElement(emptyCandidatesToInsertion); + if(componentWhereWeNest instanceof Branch) + insertRandomly(service,(CompositeStructuralComponent) componentWhereWeNest.getSubComponents().get(0)); + else + insertRandomly(service,componentWhereWeNest); + emptyCandidatesToInsertion.remove(componentWhereWeNest); + } + } + AbstractWebService myservice; + int serviceIndex=0; + for(int i=services.length+nControlFlowActivities;i generateSubMarket(AbstractWebService aws, WSCompositionQoSModel qosModel, QoSAwareWSCompositionProblemServicesMarketModel marketModel) { + Set result=new HashSet(); + int numberOfCandidates=Math.max(marketModel.getNumberOfCandidates().getValue(),2); + ConcreteWebService cws=null; + DistributionFunction qosDistribution=null; + for(int i=0;i listOfSubcomponents=componentWhereWeNest.getSubComponents(); + int index=(int)(Math.floor(Math.random()*((double)listOfSubcomponents.size()))); + listOfSubcomponents.add(index, currentComponentToDistribute); + } + + private void updateNestingLevel(StructuralComponent currentComponentToDistribute, CompositeStructuralComponent componentWhereWeNest, Map nestingLevel, List candidatesToInsertion, List emptyCandidatesToInsertion) { + int parentNestingLevel=nestingLevel.get(componentWhereWeNest); + if(currentComponentToDistribute instanceof Loop){ + nestingLevel.put((Loop)currentComponentToDistribute, parentNestingLevel+1); + candidatesToInsertion.add((Loop)currentComponentToDistribute); + emptyCandidatesToInsertion.add((Loop)currentComponentToDistribute); + emptyCandidatesToInsertion.remove(componentWhereWeNest); + }else if(currentComponentToDistribute instanceof Branch){ + for(StructuralComponent subComponent:((Branch)currentComponentToDistribute).getSubComponents()){ + candidatesToInsertion.add((CompositeStructuralComponent)subComponent); + nestingLevel.put((CompositeStructuralComponent)subComponent, parentNestingLevel+1); + } + emptyCandidatesToInsertion.add((CompositeStructuralComponent)currentComponentToDistribute); + emptyCandidatesToInsertion.remove(componentWhereWeNest); + } + } + + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/GlobalQoSConstraintsModel.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/GlobalQoSConstraintsModel.java new file mode 100644 index 0000000..663808f --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/GlobalQoSConstraintsModel.java @@ -0,0 +1,45 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.problem.model; + +import es.us.isa.qosawarewsbinding.util.DistributionFunction; + + +/** + * + * @author japarejo + */ +public class GlobalQoSConstraintsModel { + private int numberOfConstraints; + private DistributionFunction dfPercentageOfOptimality; + + public GlobalQoSConstraintsModel(int numberOfConstraints, DistributionFunction dfPercentageOfOptimality) + { + this.numberOfConstraints=numberOfConstraints; + this.dfPercentageOfOptimality=dfPercentageOfOptimality; + } + + public int getNumberOfConstraints() { + return numberOfConstraints; + } + + public DistributionFunction getDfPercentageOfOptimality() { + return dfPercentageOfOptimality; + } + + public String toString() + { + StringBuffer buffer=new StringBuffer("GlobalQoSConstraintsModel("); + buffer.append("NumberOfConstraints:"+getNumberOfConstraints()); + buffer.append(",PercentageOfOptimality:"+getDfPercentageOfOptimality()); + buffer.append(")"); + return buffer.toString(); + } + + public void setNumberOfConstraints(int numberOfConstraints) { + this.numberOfConstraints = numberOfConstraints; + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/ProblemModel.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/ProblemModel.java new file mode 100644 index 0000000..488f100 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/ProblemModel.java @@ -0,0 +1,5 @@ +package es.us.isa.qosawarewsbinding.problem.model; + +public class ProblemModel { + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/QoSAwareWSCompositionProblemConstraintsModel.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/QoSAwareWSCompositionProblemConstraintsModel.java new file mode 100644 index 0000000..f39ebce --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/QoSAwareWSCompositionProblemConstraintsModel.java @@ -0,0 +1,39 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.problem.model; + +/** + * + * @author japarejo + */ +public class QoSAwareWSCompositionProblemConstraintsModel { + private GlobalQoSConstraintsModel globalConstraintsModel; + private ServiceDependencesConstraintsModel serviceDependencesConstraintsModel; + + public QoSAwareWSCompositionProblemConstraintsModel(GlobalQoSConstraintsModel globalConstraintsModel,ServiceDependencesConstraintsModel serviceDependencesConstraintsModel) + { + this.globalConstraintsModel=globalConstraintsModel; + this.serviceDependencesConstraintsModel=serviceDependencesConstraintsModel; + } + + public GlobalQoSConstraintsModel getGlobalConstraintsModel() { + return globalConstraintsModel; + } + + public ServiceDependencesConstraintsModel getServiceDependencesConstraintsModel() { + return serviceDependencesConstraintsModel; + } + + @Override + public String toString() + { + StringBuffer buffer=new StringBuffer("ConstraintsModel(\n"); + buffer.append(globalConstraintsModel+"\n"); + buffer.append(serviceDependencesConstraintsModel+"\n"); + buffer.append(";"); + return buffer.toString(); + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/QoSAwareWSCompositionProblemModel.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/QoSAwareWSCompositionProblemModel.java new file mode 100644 index 0000000..681700d --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/QoSAwareWSCompositionProblemModel.java @@ -0,0 +1,62 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.problem.model; + +import es.us.isa.qosawarewsbinding.problem.QoSAwareWSCompositionProblem; + +/** + * + * @author japarejo + */ +public class QoSAwareWSCompositionProblemModel extends ProblemModel { + private QoSAwareWSCompositionProblemStructuralModel structuralModel; + private QoSAwareWSCompositionProblemQoSModel qosModel; + private QoSAwareWSCompositionProblemServicesMarketModel marketModel; + private QoSAwareWSCompositionProblemConstraintsModel constraintsModel; + + public QoSAwareWSCompositionProblemModel(QoSAwareWSCompositionProblemStructuralModel structuralModel, QoSAwareWSCompositionProblemQoSModel qosModel,QoSAwareWSCompositionProblemServicesMarketModel marketModel,QoSAwareWSCompositionProblemConstraintsModel constraintsModel) + { + this.structuralModel=structuralModel; + this.marketModel=marketModel; + this.qosModel=qosModel; + this.constraintsModel=constraintsModel; + } + + public QoSAwareWSCompositionProblemStructuralModel getStructuralModel() { + return structuralModel; + } + + public QoSAwareWSCompositionProblemQoSModel getQosModel() { + return qosModel; + } + + public QoSAwareWSCompositionProblemServicesMarketModel getMarketModel() { + return marketModel; + } + + public QoSAwareWSCompositionProblemConstraintsModel getConstraintsModel() { + return constraintsModel; + } + + @Override + public String toString() + { + StringBuffer buffer=new StringBuffer("=== QOS-aware Web Service Composition Problem Model ===\n"); + buffer.append("== Structural Model ==\n"); + buffer.append(structuralModel); + buffer.append("== QoS Model ==\n"); + if(qosModel!=null) + buffer.append(qosModel); + buffer.append("== Market Model ==\n"); + buffer.append(marketModel); + buffer.append("== Constraints Model ==\n"); + if(constraintsModel!=null) + buffer.append(constraintsModel); + buffer.append("=======================================================\n"); + return buffer.toString(); + } + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/QoSAwareWSCompositionProblemQoSModel.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/QoSAwareWSCompositionProblemQoSModel.java new file mode 100644 index 0000000..237a603 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/QoSAwareWSCompositionProblemQoSModel.java @@ -0,0 +1,5 @@ +package es.us.isa.qosawarewsbinding.problem.model; + +public class QoSAwareWSCompositionProblemQoSModel { + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/QoSAwareWSCompositionProblemServicesMarketModel.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/QoSAwareWSCompositionProblemServicesMarketModel.java new file mode 100644 index 0000000..59a8d57 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/QoSAwareWSCompositionProblemServicesMarketModel.java @@ -0,0 +1,53 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.problem.model; + +import java.util.HashMap; +import java.util.Map; + +import es.us.isa.qosawarewsbinding.qos.QoSProperty; +import es.us.isa.qosawarewsbinding.util.DistributionFunction; + +/** + * + * @author japarejo + */ +public class QoSAwareWSCompositionProblemServicesMarketModel { + private DistributionFunction numberOfCandidates; + private Map> qosValues; + + public QoSAwareWSCompositionProblemServicesMarketModel(DistributionFunction numberOfCandidates) { + this(numberOfCandidates, new HashMap>()); + } + + public QoSAwareWSCompositionProblemServicesMarketModel(DistributionFunction numberOfCandidates, Map> qosValues) + { + this.numberOfCandidates=numberOfCandidates; + this.qosValues=qosValues; + } + + + public DistributionFunction getNumberOfCandidates() { + return numberOfCandidates; + } + + public Map> getQosValues() { + return qosValues; + } + + @Override + public String toString() + { + StringBuffer buffer=new StringBuffer("ServicesMarquetModel("); + buffer.append("NumberOfCandidates:"+numberOfCandidates+",\n"); + for(QoSProperty property:qosValues.keySet()) + { + buffer.append(" "+property.getName()+":"+qosValues.get(property)+"\n"); + } + buffer.append(")\n"); + return buffer.toString(); + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/QoSAwareWSCompositionProblemStructuralModel.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/QoSAwareWSCompositionProblemStructuralModel.java new file mode 100644 index 0000000..df50a81 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/QoSAwareWSCompositionProblemStructuralModel.java @@ -0,0 +1,81 @@ +package es.us.isa.qosawarewsbinding.problem.model; + +import es.us.isa.qosawarewsbinding.util.DistributionFunction; + +public class QoSAwareWSCompositionProblemStructuralModel { + private int numberOfActivities; + private int numberOfAbstractServices; + private int maxNestingLevel; + private int percentageOfControlFlowActivities; + private double percentageLoops; + private DistributionFunction iterationsPerLoop; + private DistributionFunction branchesPerIf; + + public int getNumberOfActivities() { + return numberOfActivities; + } + + public void setNumberOfActivities(int numberOfActivities) { + this.numberOfActivities = numberOfActivities; + } + + public int getNumberOfAbstractServices() { + return (numberOfActivities*(100-percentageOfControlFlowActivities))/100; + } + + public int getMaxNestingLevel() { + return maxNestingLevel; + } + + public void setMaxNestingLevel(int maxNestingLevel) { + this.maxNestingLevel = maxNestingLevel; + } + + public int getPercentageOfControlFlowActivities() { + return percentageOfControlFlowActivities; + } + + public void setPercentageOfControlFlowActivities(int cyclomaticComplexity) { + this.percentageOfControlFlowActivities = cyclomaticComplexity; + } + + public double getPercentageLoops() { + return percentageLoops; + } + + public void setPercentageLoops(double percentageLoops) { + this.percentageLoops = percentageLoops; + } + + public DistributionFunction getIterationsPerLoop() { + return iterationsPerLoop; + } + + public void setIterationsPerLoop(DistributionFunction iterationsPerLoop) { + this.iterationsPerLoop = iterationsPerLoop; + } + + public DistributionFunction getBranchesPerIf() { + return branchesPerIf; + } + + public void setBranchesPerIf(DistributionFunction branchesPerIf) { + this.branchesPerIf = branchesPerIf; + } + + @Override + public String toString() + { + StringBuffer buffer=new StringBuffer("QoSAwareStructuralModel(\n"); + String prefix=" "; + buffer.append(prefix+"NActivities="+numberOfActivities+",\n"); + buffer.append(prefix+"NAbstractServices="+numberOfAbstractServices+",\n"); + buffer.append(prefix+"PercentageOfControlFlowActivities="+percentageOfControlFlowActivities+",\n"); + buffer.append(prefix+"MaxNestingLevel="+maxNestingLevel+",\n"); + buffer.append(prefix+"PercentageOfLoops="+percentageLoops+",\n"); + buffer.append(prefix+"AverageNumberOfIterations(in Loops)="+iterationsPerLoop+",\n"); + buffer.append(prefix+"AverageNumberOfBranches(in Ifs)="+branchesPerIf+",\n"); + buffer.append(prefix+")\n"); + return buffer.toString(); + } +} \ No newline at end of file diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/ServiceDependencesConstraintsModel.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/ServiceDependencesConstraintsModel.java new file mode 100644 index 0000000..751f5af --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/problem/model/ServiceDependencesConstraintsModel.java @@ -0,0 +1,14 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.problem.model; + +/** + * + * @author japarejo + */ +public class ServiceDependencesConstraintsModel { + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/QoSProperty.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/QoSProperty.java new file mode 100644 index 0000000..d7b3bd4 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/QoSProperty.java @@ -0,0 +1,67 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package es.us.isa.qosawarewsbinding.qos; + +import java.io.Serializable; + +import es.us.isa.qosawarewsbinding.util.Domain; + +/** + * + * @author japarejo + */ +public class QoSProperty implements Serializable { + + private String name; + private Domain domain; + private QoSPropertyType type; + + public QoSProperty(String name, Domain domain, QoSPropertyType type) { + this.name = name; + this.domain = domain; + this.type = type; + } + + public QoSProperty(String name, QoSPropertyType type) { + this(name, new Domain(), type); + } + + public String getName() { + return name; + } + + public Domain getDomain() { + return domain; + } + + public QoSPropertyType getType() { + return type; + } + + @Override + public boolean equals(Object value) + { + boolean result=false; + if(value instanceof QoSProperty) + { + QoSProperty vproperty=(QoSProperty)value; + result=(vproperty.name.equalsIgnoreCase(name)) ; + } + return result; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 23 * hash + (this.name != null ? this.name.hashCode() : 0); + return hash; + } + + @Override + public String toString() + { + return name+":"+type+"-"+domain; // TODO Change the separator (problems in linux!) + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/QoSPropertyType.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/QoSPropertyType.java new file mode 100644 index 0000000..b6ad042 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/QoSPropertyType.java @@ -0,0 +1,12 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.qos; + +/** + * + * @author japarejo + */ +public enum QoSPropertyType {POSITIVE,NEGATIVE} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/AggregationFunction.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/AggregationFunction.java new file mode 100644 index 0000000..f1fcafb --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/AggregationFunction.java @@ -0,0 +1,19 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.qos.aggretation; + +import java.util.List; + + + +/** + * + * @author José Antonio Parejo Maestre + */ +public interface AggregationFunction { + public Double aggregation(List values, List ponderations); + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/AverageAggregationFunction.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/AverageAggregationFunction.java new file mode 100644 index 0000000..1fccddc --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/AverageAggregationFunction.java @@ -0,0 +1,49 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.qos.aggretation; + +import java.util.List; + +/** + * + * @author José Antonio Parejo Maestre + */ +public class AverageAggregationFunction extends NumericAggregationFunction { + + private static AverageAggregationFunction _instance=null; + + private AverageAggregationFunction(){} + + public static AverageAggregationFunction getInstance() + { + if(_instance==null) + _instance=new AverageAggregationFunction(); + return _instance; + } + + @Override + public String toString() + { + return "AVG"; + } + + + public Double aggregation(List values, List ponderations) { + Double result=new Double(0.0); + int i=0; + for(Double value:values) + if(value!=null){ + if(ponderations.get(i)!=null) + result=value*ponderations.get(i)+result; + i++; + } + if(values.size()==0) + result = 0.0; + else + result = result/values.size(); + return result; + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/MaxAggregationFunction.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/MaxAggregationFunction.java new file mode 100644 index 0000000..31f5a64 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/MaxAggregationFunction.java @@ -0,0 +1,47 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.qos.aggretation; + +import java.util.List; + +/** + * + * @author José Antonio Parejo Maestre + */ +public class MaxAggregationFunction implements AggregationFunction { + + private static MaxAggregationFunction _instance=null; + + private MaxAggregationFunction(){} + + public static MaxAggregationFunction getInstance() + { + if(_instance==null) + _instance=new MaxAggregationFunction(); + return _instance; + } + + @Override + public String toString() + { + return "MAX"; + } + + + public Double aggregation(List values, List ponderations) { + Double result=Double.MIN_VALUE; + double candidate=0; + for(Double value:values) + { + candidate=value; + if(candidate>result) + result=candidate; + } + if(values.size()==0) + result=0.0; + return result; + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/MaxAverageAggregationFunction.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/MaxAverageAggregationFunction.java new file mode 100644 index 0000000..a206c78 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/MaxAverageAggregationFunction.java @@ -0,0 +1,49 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.qos.aggretation; + +import java.util.List; + +/** + * + * @author José Antonio Parejo Maestre + */ +public class MaxAverageAggregationFunction implements AggregationFunction { + + private static MaxAverageAggregationFunction _instance=null; + + private MaxAverageAggregationFunction(){} + + public static MaxAverageAggregationFunction getInstance() + { + if(_instance==null) + _instance=new MaxAverageAggregationFunction(); + return _instance; + } + + @Override + public String toString() + { + return "MAXAVG"; + } + + + public Double aggregation(List values, List ponderations) { + Double result=Double.MIN_VALUE; + double candidate=0; + for(Double value:values) + { + candidate=value; + if(candidate>result) + result=candidate; + } + if(values.size()==0) + result=0.0; + else + result = result/values.size(); + return result; + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/MinAggregationFunction.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/MinAggregationFunction.java new file mode 100644 index 0000000..72fad95 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/MinAggregationFunction.java @@ -0,0 +1,50 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.qos.aggretation; + + +import java.util.List; + +/** + * + * @author José Antonio Parejo Maestre + */ +public class MinAggregationFunction implements AggregationFunction{ + + private static MinAggregationFunction _instance=null; + + private MinAggregationFunction(){} + + public static MinAggregationFunction getInstance() + { + if(_instance==null) + _instance=new MinAggregationFunction(); + return _instance; + } + + @Override + public String toString() + { + return "MIN"; + } + + + public Double aggregation(List values, List ponderations) { + Double result=Double.MAX_VALUE; + double candidate=0; + for(Double value:values) + { + candidate=value; + if(candidate values, List ponderations) { + Double result=Double.MAX_VALUE; + double candidate=0; + for(Double value:values) + { + candidate=value; + if(candidate implements AggregationFunction { + + protected X obtainFromDouble(double value) + { + Class myclass=this.getClass(); + TypeVariable[] types=myclass.getTypeParameters(); + TypeVariable type=types[0]; + Class paramTypeClass=(Class)type.getGenericDeclaration(); + X result=null; + Class c; + if(Integer.TYPE.isAssignableFrom(paramTypeClass)) + { + result=(X)(new Integer((int)Math.round(value))); + }else if(Double.TYPE.isAssignableFrom(paramTypeClass)) + { + result=(X)(new Double(value)); + }else if(Long.TYPE.isAssignableFrom(paramTypeClass)) + { + result=(X)(new Long((long)Math.round(value))); + }else if(Float.TYPE.isAssignableFrom(paramTypeClass)) + { + result=(X)(new Float(value)); + } + return result; + } + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/ProductoryAggregationFunction.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/ProductoryAggregationFunction.java new file mode 100644 index 0000000..1a9bcb0 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/ProductoryAggregationFunction.java @@ -0,0 +1,45 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.qos.aggretation; + +import java.util.List; + +/** + * + * @author José Antonio Parejo Maestre + */ +public class ProductoryAggregationFunction implements AggregationFunction { + + private static ProductoryAggregationFunction _instance=null; + + private ProductoryAggregationFunction(){} + + public static ProductoryAggregationFunction getInstance() + { + if(_instance==null) + _instance=new ProductoryAggregationFunction(); + return _instance; + } + + @Override + public String toString() + { + return "PRODUCT"; + } + + + public Double aggregation(List values, List ponderations) { + Double result=new Double(1.0); + int i=0; + for(Double value:values){ + result=result*value*ponderations.get(i); + i++; + } + if(values.size()==0) + result=0.0; + return result; + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/ProductoryPowAggregationFunction.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/ProductoryPowAggregationFunction.java new file mode 100644 index 0000000..86ac3d8 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/ProductoryPowAggregationFunction.java @@ -0,0 +1,44 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.qos.aggretation; + +import java.util.List; + +/** + * + * @author japarejo + */ +public class ProductoryPowAggregationFunction implements AggregationFunction { + private static ProductoryPowAggregationFunction _instance=null; + + private ProductoryPowAggregationFunction(){} + + public static ProductoryPowAggregationFunction getInstance() + { + if(_instance==null) + _instance=new ProductoryPowAggregationFunction(); + return _instance; + } + + + public Double aggregation(List values, List ponderations) { + double result=1; + for(Double value:values) + if(value!=0) + result*=value; + double ponderation=1; + if(ponderations.size()>0) + ponderation=ponderations.get(0); + result=Math.pow(result, ponderation); + return result; + } + + @Override + public String toString() + { + return "POW"; + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/SumatoryAggregationFunction.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/SumatoryAggregationFunction.java new file mode 100644 index 0000000..3df8fb9 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/SumatoryAggregationFunction.java @@ -0,0 +1,46 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.qos.aggretation; + +import java.util.List; + +/** + * + * @author José Antonio Parejo Maestre + */ +public class SumatoryAggregationFunction extends NumericAggregationFunction { + + private static SumatoryAggregationFunction _instance=null; + + private SumatoryAggregationFunction(){} + + public static SumatoryAggregationFunction getInstance() + { + if(_instance==null) + _instance=new SumatoryAggregationFunction(); + return _instance; + } + + @Override + public String toString() + { + return "SUM"; + } + + + public Double aggregation(List values, List ponderations) { + Double result=new Double(0.0); + int i=0; + for(Double value:values) + if(value!=null){ + if(ponderations.get(i)!=null) + result=value*ponderations.get(i)+result; + i++; + } + return result; + } + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/SumatoryPowAggregationFunction.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/SumatoryPowAggregationFunction.java new file mode 100644 index 0000000..46ea87b --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/qos/aggretation/SumatoryPowAggregationFunction.java @@ -0,0 +1,44 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.qos.aggretation; + +import java.util.List; + +/** + * + * @author japarejo + */ +public class SumatoryPowAggregationFunction implements AggregationFunction { + + private static SumatoryPowAggregationFunction _instance=null; + + private SumatoryPowAggregationFunction(){} + + public static SumatoryPowAggregationFunction getInstance() + { + if(_instance==null) + _instance=new SumatoryPowAggregationFunction(); + return _instance; + } + + + public Double aggregation(List values, List ponderations) { + double result=0; + for(Double value:values) + result+=value; + double ponderation=1; + if(ponderations.size()>0) + ponderation=ponderations.get(0); + result=Math.pow(result, ponderation); + return result; + } + + public String toString() + { + return "SUMPOW"; + } + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/solution/AbstractSolution.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/solution/AbstractSolution.java new file mode 100644 index 0000000..a69c51f --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/solution/AbstractSolution.java @@ -0,0 +1,63 @@ +package es.us.isa.qosawarewsbinding.solution; + +import es.us.isa.qosawarewsbinding.problem.Problem; + +public abstract class AbstractSolution implements Solution { + + protected double fitness; + protected T problem; + + public AbstractSolution(T problem) { + this.problem = problem; + fitness = Double.MAX_VALUE; + } + + public double getFitness() { + if (fitness == Double.MAX_VALUE && problem != null) + fitness = problem.fitness(this); + return fitness; + } + + public abstract Solution createRandom(); + + /** + * Getter for property problem. + * + * @return Value of property problem. + */ + public T getProblem() { + return problem; + } + + /** + * Setter for property problem. + * + * @param problem + * New value of property problem. + */ + public void setProblem(T problem) { + this.problem = problem; + } + + /** + * Funcin de ordenacin natural en base al ndice de evaluacin. Por defecto, + * si el objeto no es una solucin se consideran iguales. + */ + + public int compareTo(Object obj) { + int result = 0; + if (obj instanceof Solution) { + if (this.fitness > ((Solution) obj).getFitness()) + result = 1; + else if (this.fitness < ((Solution) obj).getFitness()) + result = -1; + } + return result; + } + + @Override + public String toString() { + return "Fitness:" + fitness; + } + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/solution/QoSAwareWSCompositionSolution.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/solution/QoSAwareWSCompositionSolution.java new file mode 100644 index 0000000..28df79c --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/solution/QoSAwareWSCompositionSolution.java @@ -0,0 +1,60 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.solution; + +import java.util.Map; + +import es.us.isa.qosawarewsbinding.AbstractWebService; +import es.us.isa.qosawarewsbinding.ConcreteWebService; +import es.us.isa.qosawarewsbinding.problem.QoSAwareWSCompositionProblem; + +/** + * + * @author japarejo + */ +public abstract class QoSAwareWSCompositionSolution extends + AbstractSolution { + + public QoSAwareWSCompositionSolution(QoSAwareWSCompositionProblem problem) { + super(problem); + } + + public abstract ConcreteWebService getSelectedService(AbstractWebService aws); + + public abstract void setSelectedService(AbstractWebService aws, + ConcreteWebService cws); + + public boolean isUsingServices( + Map cwservices) { + boolean result = true; + int i = 0; + for (AbstractWebService aws : cwservices.keySet()) { + if (!isUsing(aws, cwservices.get(aws))) + return false; + } + return result; + } + + public boolean isUsing(AbstractWebService aws, ConcreteWebService cws) { + return cws == getSelectedService(aws); + } + + @Override + public String toString() { + StringBuffer buffer = new StringBuffer("("); + QoSAwareWSCompositionProblem myproblem = (QoSAwareWSCompositionProblem) problem; + for (AbstractWebService aws : myproblem.getStructure().getComponents()) { + buffer.append("|"); + buffer.append(aws.toString()); + buffer.append("->"); + buffer.append(getSelectedService(aws)); + buffer.append("|"); + } + buffer.append("),"); + buffer.append(super.toString()); + return buffer.toString(); + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/solution/Solution.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/solution/Solution.java new file mode 100644 index 0000000..9a0d7f5 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/solution/Solution.java @@ -0,0 +1,8 @@ +package es.us.isa.qosawarewsbinding.solution; + + +public interface Solution extends Comparable,Cloneable { + public double getFitness(); + public Solution createRandom(); + public int compareTo(Object obj); +} \ No newline at end of file diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/solution/vector/QoSAwareWSCompositionProblemInteger.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/solution/vector/QoSAwareWSCompositionProblemInteger.java new file mode 100644 index 0000000..9a2828c --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/solution/vector/QoSAwareWSCompositionProblemInteger.java @@ -0,0 +1,184 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.solution.vector; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import es.us.isa.qosawarewsbinding.AbstractWebService; +import es.us.isa.qosawarewsbinding.ConcreteWebService; +import es.us.isa.qosawarewsbinding.WSCompositionStructure; +import es.us.isa.qosawarewsbinding.problem.ExecutionPath; +import es.us.isa.qosawarewsbinding.problem.QoSAwareWSCompositionProblem; +import es.us.isa.qosawarewsbinding.problem.WSCompositionConstraint; +import es.us.isa.qosawarewsbinding.qos.QoSProperty; +import es.us.isa.qosawarewsbinding.qos.QoSPropertyType; +import es.us.isa.qosawarewsbinding.solution.QoSAwareWSCompositionSolution; +import es.us.isa.qosawarewsbinding.solution.Solution; + +/** + * + * @author japarejo + */ +public class QoSAwareWSCompositionProblemInteger extends QoSAwareWSCompositionProblem { + + private Map abstractWebServicesIndexes; + private QoSAwareWSCompositionProblem problem; + private ConcreteWebService[][] services; + + + public QoSAwareWSCompositionProblemInteger(QoSAwareWSCompositionProblem problem) + { + super(problem.getStructure(),problem.getQosmodel()); + this.problem=problem; + abstractWebServicesIndexes=new HashMap(); + int numberofAbstractServices=problem.getMarket().keySet().size(); + services=new ConcreteWebService[numberofAbstractServices][]; + int i=0; + int j=0; + for(AbstractWebService aws:problem.getMarket().keySet()){ + abstractWebServicesIndexes.put(aws, i); + services[i]=new ConcreteWebService[problem.getMarket().get(aws).size()]; + j=0; + for(ConcreteWebService service:problem.getMarket().get(aws)){ + services[i][j]=service; + j++; + } + i++; + } + } + + @Override + public double feasibilityDistance(Solution sol) { + return problem.feasibilityDistance(sol); + } + + @Override + public double feasibilityFreeFitness(Solution sol) { + return problem.feasibilityFreeFitness(sol); + } + + + @Override + public int numberOfCandidates(AbstractWebService aws) + { + return problem.numberOfCandidates(aws); + } + + @Override + public Map> getMarket() { + return problem.getMarket(); + } + + @Override + public WSCompositionStructure getStructure() + { + return problem.getStructure(); + } + + public int numberOfCandidates(int aws) + { + return services[aws].length; + } + + public ConcreteWebService getService(int aws, int index) + { + return services[aws][index]; + } + + public int getIndex(AbstractWebService aws) + { + return abstractWebServicesIndexes.get(aws); + } + + int getServiceIndex(int aws, ConcreteWebService cws) { + ConcreteWebService []concreteservices=services[aws]; + int result=-1; + for(int i=0;i getExpaths() { + return problem.getExpaths(); + } + + public double numberOfExecutedTasks() + { + return problem.numberOfExecutedTasks(); + } + + @Override + public List getConstraints() { + return problem.getConstraints(); + } + + public Double bestValue(QoSProperty property) { + QoSAwareWSCompositionSolution solution = computeBestSolution(property); + return getQosmodel().evaluate(solution, property, getStructure()); + } + + public Double worstValue(QoSProperty property) { + QoSAwareWSCompositionSolution solution = computeWorstSolution(property); + return getQosmodel().evaluate(solution, property, getStructure()); + } + + public QoSAwareWSCompositionSolution computeBestSolution(QoSProperty property) { + QoSAwareWSCompositionVectorSolution result = new QoSAwareWSCompositionVectorSolution(this); + ConcreteWebService bestCandidate; + ConcreteWebService cws; + Set abstractServices = this.abstractWebServicesIndexes.keySet(); + int i; + for(AbstractWebService aws: abstractServices){ + i = this.abstractWebServicesIndexes.get(aws); + bestCandidate = services[i][0]; + for(int j=0; j ((Double) bestCandidate.getQoSValue(property))) { + bestCandidate = cws; + } + } else { + if (((Double) cws.getQoSValue(property)) < ((Double) bestCandidate.getQoSValue(property))) { + bestCandidate = cws; + } + } + } + result.setSelectedService(aws, bestCandidate); + } + return result; + } + + public QoSAwareWSCompositionSolution computeWorstSolution(QoSProperty property) { + QoSAwareWSCompositionVectorSolution result = new QoSAwareWSCompositionVectorSolution(this); + ConcreteWebService worstCandidate = null; + ConcreteWebService cws; + Set abstractServices = this.abstractWebServicesIndexes.keySet(); + int i; + for(AbstractWebService aws: abstractServices){ + i = this.abstractWebServicesIndexes.get(aws); + worstCandidate = services[i][0]; + for(int j=0; j ((Double) worstCandidate.getQoSValue(property))) { + worstCandidate = cws; + } + } + } + result.setSelectedService(aws, worstCandidate); + } + return result; + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/solution/vector/QoSAwareWSCompositionVectorMovement.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/solution/vector/QoSAwareWSCompositionVectorMovement.java new file mode 100644 index 0000000..d510fe5 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/solution/vector/QoSAwareWSCompositionVectorMovement.java @@ -0,0 +1,99 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.solution.vector; + + +/** + * + * @author japarejo + */ +public class QoSAwareWSCompositionVectorMovement { + + private int abstractWebService; + private int previousSelectedService; + private int newSelectedService; + + public QoSAwareWSCompositionVectorMovement(QoSAwareWSCompositionVectorSolutionNavigable solution) + { + + QoSAwareWSCompositionProblemInteger problem=(QoSAwareWSCompositionProblemInteger)solution.getProblem(); + int services=problem.getMarket().keySet().size(); + while(abstractWebService=problem.numberOfCandidates(result.abstractWebService)){ + result.abstractWebService++; + if(result.abstractWebService==services){ + result.abstractWebService=0; + } + while(result.abstractWebService1) + result.newSelectedService=1; + } + result.previousSelectedService=solution.getSelectedService(result.abstractWebService); + return result; + } + + @Override + public String toString() + { + return "["+abstractWebService+","+previousSelectedService+","+newSelectedService+"]"; + } + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/solution/vector/QoSAwareWSCompositionVectorSolution.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/solution/vector/QoSAwareWSCompositionVectorSolution.java new file mode 100644 index 0000000..837594f --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/solution/vector/QoSAwareWSCompositionVectorSolution.java @@ -0,0 +1,96 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.solution.vector; + +import es.us.isa.qosawarewsbinding.AbstractWebService; +import es.us.isa.qosawarewsbinding.ConcreteWebService; +import es.us.isa.qosawarewsbinding.problem.QoSAwareWSCompositionProblem; +import es.us.isa.qosawarewsbinding.solution.QoSAwareWSCompositionSolution; +import es.us.isa.qosawarewsbinding.solution.Solution; + +/** + * + * @author japarejo + */ +public class QoSAwareWSCompositionVectorSolution extends QoSAwareWSCompositionSolution { + + protected int[] selectedServicesIndexes; + + public QoSAwareWSCompositionVectorSolution(QoSAwareWSCompositionProblem problem) + { + super(problem); + if(!(problem instanceof QoSAwareWSCompositionProblemInteger)) + this.problem=new QoSAwareWSCompositionProblemInteger(problem); + selectedServicesIndexes=new int[problem.getMarket().keySet().size()]; + //for(int i=0;i=0 && aws listTasks = new ArrayList(tasks); + for (int i = 0; i < tasks; i++) + if (theproblem.numberOfCandidates(i) > 1) + listTasks.add(new Integer(i)); + int randomAWS = (int) Math.floor(Math.random() + * ((double) listTasks.size())); + int tries = 0; + while (theproblem.numberOfCandidates(randomAWS) < 2 && tries < 5) { + randomAWS = (int) Math.floor(Math.random() + * ((double) listTasks.size())); + tries++; + } + int nCandidates = theproblem.numberOfCandidates(randomAWS); + if (nCandidates > 1) { + List listCandidates = new ArrayList( + nCandidates - 2); + for (int i = 0; i < nCandidates; i++) + if (selectedServicesIndexes[randomAWS] != i) + listCandidates.add(new Integer(i)); + int randomCandidateIndex = (int) Math.floor(Math.random() + * ((double) listCandidates.size())); + result = new QoSAwareWSCompositionVectorSolutionNavigable( + theproblem); + System.arraycopy(selectedServicesIndexes, 0, + result.selectedServicesIndexes, 0, + selectedServicesIndexes.length); + result.selectedServicesIndexes[randomAWS] = listCandidates + .get(randomCandidateIndex); + } + return result; + } + + @Override + public Solution createRandom() { + return new QoSAwareWSCompositionVectorSolutionNavigable( + (QoSAwareWSCompositionProblem) problem); + } + + public void resetNeighbourhood() { + QoSAwareWSCompositionProblemInteger theproblem = (QoSAwareWSCompositionProblemInteger) problem; + int tasks = selectedServicesIndexes.length; + movement = new QoSAwareWSCompositionVectorMovement(this); + neighboursToExplore = 0; + for (int i = 0; i < tasks; i++) { + neighboursToExplore += theproblem.numberOfCandidates(i) - 1; + } + } + + @Override + public String toString() { + return super.toString(); + } + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/BoundedDomain.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/BoundedDomain.java new file mode 100644 index 0000000..9160f9c --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/BoundedDomain.java @@ -0,0 +1,72 @@ +package es.us.isa.qosawarewsbinding.util; + + +public class BoundedDomain extends IntensionDomain { + + + private ComparableType minBound; + + + private ComparableType maxBound; + + + public BoundedDomain () { + super(); + } + + public BoundedDomain(ComparableType valuemin, ComparableType valuemax) + { + minBound=valuemin; + maxBound=valuemax; + } + + + public ComparableType getMaxBound () { + return maxBound; + } + + public void setMaxBound (ComparableType val) { + this.maxBound = val; + } + + public ComparableType getMinBound () { + return minBound; + } + + public void setMinBound (ComparableType val) { + this.minBound = val; + } + + @Override + public boolean predicate (ComparableType value) { + return minBound.doubleValue()<=value.doubleValue() && maxBound.doubleValue()>=value.doubleValue(); + } + + @Override + public boolean equals(Object value) + { + boolean result=false; + if(value instanceof BoundedDomain) + { + BoundedDomain dom=(BoundedDomain)value; + result=minBound.equals(dom.getMinBound()) && maxBound.equals(dom.getMaxBound()); + } + return result; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 17 * hash + (this.minBound != null ? this.minBound.hashCode() : 0); + hash = 17 * hash + (this.maxBound != null ? this.maxBound.hashCode() : 0); + return hash; + } + + @Override + public String toString() + { + return minBound.getClass().getSimpleName()+"["+minBound.toString()+","+maxBound.toString()+"]"; + } +} + + diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/BoundedDoubleGaussianDistributionFunction.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/BoundedDoubleGaussianDistributionFunction.java new file mode 100644 index 0000000..583e73a --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/BoundedDoubleGaussianDistributionFunction.java @@ -0,0 +1,54 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.util; + +/** + * + * @author japarejo + */ +public class BoundedDoubleGaussianDistributionFunction extends DoubleGaussianDistributionFunction { + private double min; + private double max; + public BoundedDoubleGaussianDistributionFunction(double mean, double standardDeviation) + { + this(mean,standardDeviation,Double.MIN_VALUE,Double.MAX_VALUE); + } + + public BoundedDoubleGaussianDistributionFunction(double mean, double standardDeviation, double min, double max) + { + super(mean,standardDeviation); + this.min=min; + this.max=max; + } + + @Override + public Double getValue() { + Double result=super.getValue(); + if(resultgetMax()) + result=getMax(); + return result; + } + + public double getMin() { + return min; + } + + public void setMin(double min) { + this.min = min; + } + + public double getMax() { + return max; + } + + public void setMax(double max) { + this.max = max; + } + + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/DistributionFunction.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/DistributionFunction.java new file mode 100644 index 0000000..620cfff --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/DistributionFunction.java @@ -0,0 +1,14 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.util; + +/** + * + * @author japarejo + */ +public interface DistributionFunction { + public T getValue(); +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/Domain.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/Domain.java new file mode 100644 index 0000000..4191cf0 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/Domain.java @@ -0,0 +1,11 @@ +package es.us.isa.qosawarewsbinding.util; + +public class Domain { + public Domain() { + } + + public boolean belongs(T value) { + return true; + } + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/DoubleGaussianDistributionFunction.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/DoubleGaussianDistributionFunction.java new file mode 100644 index 0000000..df2ae45 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/DoubleGaussianDistributionFunction.java @@ -0,0 +1,55 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package es.us.isa.qosawarewsbinding.util; + +import org.apache.commons.math.random.RandomData; +import org.apache.commons.math.random.RandomDataImpl; + + +/** + * + * @author japarejo + */ +public class DoubleGaussianDistributionFunction implements DistributionFunction { + + protected Double mean; + protected Double standarDeviation; + RandomData dataGenerator; + + public DoubleGaussianDistributionFunction(double mean, double standardDeviation) { + this.mean = mean; + this.standarDeviation = standardDeviation; + dataGenerator = new RandomDataImpl(); + } + + + public Double getValue() { + Double result = mean; + result = dataGenerator.nextGaussian(mean, standarDeviation); + return result; + } + + public double getMean() { + return mean; + } + + public void setMean(double mean) { + this.mean = mean; + } + + public double getStandarDeviation() { + return standarDeviation; + } + + public void setStandarDeviation(double standardDeviation) { + this.standarDeviation = standardDeviation; + } + + @Override + public String toString() + { + return "Gaussian(Mean:"+mean+",StandardDeviation:"+standarDeviation+")"; + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/DoubleUniformDistributionFunction.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/DoubleUniformDistributionFunction.java new file mode 100644 index 0000000..94f4aea --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/DoubleUniformDistributionFunction.java @@ -0,0 +1,51 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.util; + +/** + * + * @author japarejo + */ +public class DoubleUniformDistributionFunction implements DistributionFunction { + + private Double min; + private Double max; + + public DoubleUniformDistributionFunction(Double min, Double max) + { + this.min=min; + this.max=max; + } + + + public Double getValue() { + return getMin()+Math.random()*(getMax()-getMin()); + + } + + public Double getMin() { + return min; + } + + public void setMin(Double min) { + this.min = min; + } + + public Double getMax() { + return max; + } + + public void setMax(Double max) { + this.max = max; + } + + @Override + public String toString() + { + return "Uniform(Min:"+min+",Max:"+max+")"; + } + +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/IntegerGausssianDistributionFunction.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/IntegerGausssianDistributionFunction.java new file mode 100644 index 0000000..ae6d4a6 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/IntegerGausssianDistributionFunction.java @@ -0,0 +1,55 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.util; + +import org.apache.commons.math.random.RandomData; +import org.apache.commons.math.random.RandomDataImpl; + +/** + * + * @author japarejo + */ +public class IntegerGausssianDistributionFunction implements DistributionFunction{ + + Integer mean; + Double standarDeviation; + RandomData dataGenerator; + + public IntegerGausssianDistributionFunction(Integer mean, Double standarDeviation) + { + this.mean=mean; + this.standarDeviation=standarDeviation; + dataGenerator=new RandomDataImpl(); + } + + + public Integer getValue() { + Integer result = mean; + result = new Integer((int)Math.round((float)(dataGenerator.nextGaussian(mean, standarDeviation)))); + return result; + } + + public Integer getMean() { + return mean; + } + + public void setMean(Integer mean) { + this.mean = mean; + } + + public double getStandarDeviation() { + return standarDeviation; + } + + public void setStandarDeviation(double standardDeviation) { + this.standarDeviation = standardDeviation; + } + + public String toString() + { + return "Gaussian(Mean:"+mean+",StandardDeviation:"+standarDeviation+")"; + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/IntegerUniformDistributionFunction.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/IntegerUniformDistributionFunction.java new file mode 100644 index 0000000..d0f1537 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/IntegerUniformDistributionFunction.java @@ -0,0 +1,50 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package es.us.isa.qosawarewsbinding.util; + +/** + * + * @author japarejo + */ +public class IntegerUniformDistributionFunction implements DistributionFunction { + + private Integer min; + private Integer max; + + public IntegerUniformDistributionFunction(Integer min, Integer max) + { + this.min=min; + this.max=max; + } + + + public Integer getValue() { + double value=getMin().doubleValue()+Math.random()*(getMax().doubleValue()-getMin().doubleValue()); + return (int)Math.round(value); + } + + public Integer getMin() { + return min; + } + + public void setMin(Integer min) { + this.min = min; + } + + public Integer getMax() { + return max; + } + + public void setMax(Integer max) { + this.max = max; + } + + @Override + public String toString() + { + return "Uniform(Min:"+min+",Max:"+max+")"; + } +} diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/IntensionDomain.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/IntensionDomain.java new file mode 100644 index 0000000..0688204 --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/IntensionDomain.java @@ -0,0 +1,16 @@ +package es.us.isa.qosawarewsbinding.util; + + +public abstract class IntensionDomain extends Domain { + + public IntensionDomain () { + } + + public boolean belongs (T value) { + return predicate(value); + } + + public abstract boolean predicate (T value); + +} + diff --git a/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/MyStringTokenizer.java b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/MyStringTokenizer.java new file mode 100644 index 0000000..81d8cea --- /dev/null +++ b/engines/random-search/src/main/java/es/us/isa/qosawarewsbinding/util/MyStringTokenizer.java @@ -0,0 +1,70 @@ +package es.us.isa.qosawarewsbinding.util; + +public class MyStringTokenizer { + + int nDelimiters; + String input; + String separator; + char startDelimiter; + char endDelimiter; + /** Creates new MyStringTokenizer */ + public MyStringTokenizer(String entrada,String separador,char startDelimiter, char endDelimiter) + { + this.input=entrada; + this.separator=separador; + nDelimiters=0; + this.startDelimiter=startDelimiter; + this.endDelimiter=endDelimiter; + } + public MyStringTokenizer(String entrada,String separador) { + this(entrada,separador,'(',')'); + } + + public String nextToken(String separador) + { + this.separator=separador; + return nextToken(); + } + public String nextToken() + { + String resultado=""; + int indice=input.indexOf(separator); + if(indice<0){ + resultado=input; + input=""; + return resultado; + } + + nDelimiters=countDelimiters(input.substring(0,indice)); + while(nDelimiters!=0) + { + indice=input.indexOf(separator,indice+separator.length()); + nDelimiters=countDelimiters(input.substring(0,indice+separator.length())); + } + if(indice<0){ + resultado=input; + input=""; + }else + { + resultado=input.substring(0,indice); + input=input.substring(indice+separator.length(),input.length()); + } + return resultado; + } + + public int countDelimiters(String s) + { + int parentesis=0; + for(int i=0;i=", "value": 0.9 - }, - { - "id": "dc1", "kind": "dependency", "type": "different_provider", "tasks": ["t1", "t2"] - } - ], - "objective": { - "type": "weighted_sum", - "weights": { "cost": 1.0 }, - "normalized": true - } - } -} diff --git a/examples/constraints-global-strict.json b/examples/constraints-global-strict.json deleted file mode 100644 index dfdb1e4..0000000 --- a/examples/constraints-global-strict.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "engine_id": "minizinc-csp", - "instance": { - "metadata": { - "id": "constraints-global-strict", - "name": "Constraints: Strict Global Budget", - "version": "1.0", - "created_at": "2024-01-01T00:00:00Z" - }, - "providers": [{"id": "p1", "name": "Provider 1"}], - "tasks": [ - {"id": "t1", "name": "Task 1"}, - {"id": "t2", "name": "Task 2"} - ], - "candidates": [ - { "id": "t1_cheap", "task_id": "t1", "provider_id": "p1", "qos": { "cost": 1, "time": 100, "reliability": 0.5, "availability": 0.5, "security": 0.5 } }, - { "id": "t1_expensive", "task_id": "t1", "provider_id": "p1", "qos": { "cost": 10, "time": 10, "reliability": 0.9, "availability": 0.9, "security": 0.9 } }, - { "id": "t2_cheap", "task_id": "t2", "provider_id": "p1", "qos": { "cost": 1, "time": 100, "reliability": 0.5, "availability": 0.5, "security": 0.5 } }, - { "id": "t2_expensive", "task_id": "t2", "provider_id": "p1", "qos": { "cost": 10, "time": 10, "reliability": 0.9, "availability": 0.9, "security": 0.9 } } - ], - "features": [ - { "id": "cost", "name": "Cost", "direction": "minimize", "unit": "USD", "scale": "ratio", "valid_range": {"min": 0, "max": 100}, "normalization": { "type": "minmax", "bounds": { "min": 0, "max": 20 } } }, - { "id": "time", "name": "Time", "direction": "minimize", "unit": "ms", "scale": "ratio", "valid_range": {"min": 0, "max": 1000}, "normalization": { "type": "minmax", "bounds": { "min": 0, "max": 100 } } }, - { "id": "reliability", "name": "Rel", "direction": "maximize", "unit": "prob", "scale": "ratio", "valid_range": {"min": 0, "max": 1}, "normalization": { "type": "minmax", "bounds": { "min": 0, "max": 1 } } }, - { "id": "availability", "name": "Avail", "direction": "maximize", "unit": "prob", "scale": "ratio", "valid_range": {"min": 0, "max": 1}, "normalization": { "type": "minmax", "bounds": { "min": 0, "max": 1 } } }, - { "id": "security", "name": "Sec", "direction": "maximize", "unit": "prob", "scale": "ratio", "valid_range": {"min": 0, "max": 1}, "normalization": { "type": "minmax", "bounds": { "min": 0, "max": 1 } } } - ], - "composition": { - "type": "structured", - "root": { - "id": "root", - "kind": "SEQ", - "children": [ - { "id": "n1", "kind": "TASK", "task_id": "t1" }, - { "id": "n2", "kind": "TASK", "task_id": "t2" } - ] - } - }, - "aggregation_policies": {}, - "constraints": [ - { - "id": "gc1", "kind": "attribute_bound", "scope": "global", - "attribute_id": "cost", "op": "<=", "value": 2.0 - } - ], - "objective": { - "type": "weighted_sum", - "weights": { "time": 1.0 }, - "normalized": true - } - } -} diff --git a/examples/constraints-infeasible.json b/examples/constraints-infeasible.json deleted file mode 100644 index f7134d4..0000000 --- a/examples/constraints-infeasible.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "engine_id": "minizinc-csp", - "instance": { - "metadata": { - "id": "constraints-infeasible", - "name": "Constraints: Infeasible", - "version": "1.0", - "created_at": "2024-01-01T00:00:00Z" - }, - "providers": [{"id": "p1", "name": "Provider 1"}], - "tasks": [ - {"id": "t1", "name": "Task 1"} - ], - "candidates": [ - { "id": "c1", "task_id": "t1", "provider_id": "p1", "qos": { "cost": 1, "time": 10, "reliability": 0.99, "availability": 0.99, "security": 0.99 } } - ], - "features": [ - { "id": "cost", "name": "Cost", "direction": "minimize", "unit": "USD", "scale": "ratio", "valid_range": {"min": 0, "max": 100}, "normalization": { "type": "minmax", "bounds": { "min": 0, "max": 20 } } }, - { "id": "time", "name": "Time", "direction": "minimize", "unit": "ms", "scale": "ratio", "valid_range": {"min": 0, "max": 1000}, "normalization": { "type": "minmax", "bounds": { "min": 0, "max": 100 } } }, - { "id": "reliability", "name": "Rel", "direction": "maximize", "unit": "prob", "scale": "ratio", "valid_range": {"min": 0, "max": 1}, "normalization": { "type": "minmax", "bounds": { "min": 0, "max": 1 } } }, - { "id": "availability", "name": "Avail", "direction": "maximize", "unit": "prob", "scale": "ratio", "valid_range": {"min": 0, "max": 1}, "normalization": { "type": "minmax", "bounds": { "min": 0, "max": 1 } } }, - { "id": "security", "name": "Sec", "direction": "maximize", "unit": "prob", "scale": "ratio", "valid_range": {"min": 0, "max": 1}, "normalization": { "type": "minmax", "bounds": { "min": 0, "max": 1 } } } - ], - "composition": { - "type": "structured", - "root": { - "id": "root", - "kind": "TASK", "task_id": "t1" - } - }, - "aggregation_policies": {}, - "constraints": [ - { - "id": "impossible_rel", "kind": "attribute_bound", "scope": "local", "task_id": "t1", - "attribute_id": "reliability", "op": ">=", "value": 1.0 - } - ], - "objective": { - "type": "weighted_sum", - "weights": { "cost": 1.0 }, - "normalized": true - } - } -} diff --git a/examples/constraints-same-provider.json b/examples/constraints-same-provider.json deleted file mode 100644 index 9db38d6..0000000 --- a/examples/constraints-same-provider.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "engine_id": "minizinc-csp", - "instance": { - "metadata": { - "id": "constraints-same-provider", - "name": "Constraints: Same Provider Affinity", - "version": "1.0", - "created_at": "2024-01-01T00:00:00Z" - }, - "providers": [ - {"id": "p1", "name": "Provider 1"}, - {"id": "p2", "name": "Provider 2"} - ], - "tasks": [ - {"id": "t1", "name": "Task 1"}, - {"id": "t2", "name": "Task 2"} - ], - "candidates": [ - { "id": "t1_p1_great", "task_id": "t1", "provider_id": "p1", "qos": { "cost": 10, "time": 10, "reliability": 0.99, "availability": 0.9, "security": 0.9 } }, - { "id": "t1_p2_bad", "task_id": "t1", "provider_id": "p2", "qos": { "cost": 10, "time": 100, "reliability": 0.1, "availability": 0.9, "security": 0.9 } }, - - { "id": "t2_p1_bad", "task_id": "t2", "provider_id": "p1", "qos": { "cost": 10, "time": 100, "reliability": 0.1, "availability": 0.9, "security": 0.9 } }, - { "id": "t2_p2_great", "task_id": "t2", "provider_id": "p2", "qos": { "cost": 10, "time": 10, "reliability": 0.99, "availability": 0.9, "security": 0.9 } } - ], - "features": [ - { "id": "cost", "name": "Cost", "direction": "minimize", "unit": "USD", "scale": "ratio", "valid_range": {"min": 0, "max": 1000}, "normalization": { "type": "minmax", "bounds": { "min": 0, "max": 100 } } }, - { "id": "time", "name": "Time", "direction": "minimize", "unit": "ms", "scale": "ratio", "valid_range": {"min": 0, "max": 1000}, "normalization": { "type": "minmax", "bounds": { "min": 0, "max": 1000 } } }, - { "id": "reliability", "name": "Rel", "direction": "maximize", "unit": "prob", "scale": "ratio", "valid_range": {"min": 0, "max": 1}, "normalization": { "type": "minmax", "bounds": { "min": 0, "max": 1 } } }, - { "id": "availability", "name": "Avail", "direction": "maximize", "unit": "prob", "scale": "ratio", "valid_range": {"min": 0, "max": 1}, "normalization": { "type": "minmax", "bounds": { "min": 0, "max": 1 } } }, - { "id": "security", "name": "Sec", "direction": "maximize", "unit": "prob", "scale": "ratio", "valid_range": {"min": 0, "max": 1}, "normalization": { "type": "minmax", "bounds": { "min": 0, "max": 1 } } } - ], - "composition": { - "type": "structured", - "root": { - "id": "root", - "kind": "SEQ", - "children": [ - { "id": "n1", "kind": "TASK", "task_id": "t1" }, - { "id": "n2", "kind": "TASK", "task_id": "t2" } - ] - } - }, - "aggregation_policies": {}, - "constraints": [ - { - "id": "dep1", "kind": "dependency", "type": "same_provider", "tasks": ["t1", "t2"] - } - ], - "objective": { - "type": "weighted_sum", - "weights": { "reliability": 1.0 }, - "normalized": true - } - } -} diff --git a/examples/minimal-1task.json b/examples/minimal-1task.json deleted file mode 100644 index 88ba501..0000000 --- a/examples/minimal-1task.json +++ /dev/null @@ -1,287 +0,0 @@ -{ - "engine_id": "minizinc-csp", - "instance": { - "metadata": { - "id": "ex-001", - "name": "Minimal Task", - "version": "1.0", - "created_at": "2024-01-01T00:00:00Z" - }, - "providers": [ - { - "id": "p1", - "name": "Provider 1" - } - ], - "tasks": [ - { - "id": "t1", - "name": "Task 1" - } - ], - "candidates": [ - { - "id": "c1a", - "task_id": "t1", - "provider_id": "p1", - "qos": { - "cost": 10, - "time": 100 - } - }, - { - "id": "c1b", - "task_id": "t1", - "provider_id": "p1", - "qos": { - "cost": 20, - "time": 50 - } - } - ], - "features": [ - { - "id": "cost", - "name": "Cost", - "direction": "minimize", - "unit": "USD", - "scale": "ratio", - "valid_range": { - "min": 0 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 100 - }, - "increasing_is_better": false - } - }, - { - "id": "time", - "name": "Time", - "direction": "minimize", - "unit": "ms", - "scale": "ratio", - "valid_range": { - "min": 0, - "max": 10000 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 100 - }, - "increasing_is_better": false - } - }, - { - "id": "reliability", - "name": "Reliability", - "direction": "maximize", - "unit": "", - "scale": "ratio", - "valid_range": { - "min": 0, - "max": 1 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - }, - "increasing_is_better": true - } - }, - { - "id": "availability", - "name": "Availability", - "direction": "maximize", - "unit": "", - "scale": "ratio", - "valid_range": { - "min": 0, - "max": 1 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - }, - "increasing_is_better": true - } - }, - { - "id": "security", - "name": "Security", - "direction": "maximize", - "unit": "", - "scale": "ratio", - "valid_range": { - "min": 0, - "max": 1 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - }, - "increasing_is_better": true - } - } - ], - "composition": { - "type": "structured", - "root": { - "id": "n1", - "kind": "TASK", - "task_id": "t1" - } - }, - "aggregation_policies": { - "cost": { - "attribute_id": "cost", - "direction": "minimize", - "scale": "ratio", - "neutral": 0, - "compose": { - "seq": { - "fn": "sum" - }, - "and": { - "fn": "sum" - }, - "xor": { - "fn": "weighted_sum", - "expr": "sum" - } - }, - "normalize": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - }, - "increasing_is_better": false - } - }, - "time": { - "attribute_id": "time", - "direction": "minimize", - "scale": "ratio", - "neutral": 0, - "compose": { - "seq": { - "fn": "sum" - }, - "and": { - "fn": "max" - }, - "xor": { - "fn": "weighted_sum", - "expr": "sum" - } - }, - "normalize": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - }, - "increasing_is_better": false - } - }, - "reliability": { - "attribute_id": "reliability", - "direction": "maximize", - "scale": "ratio", - "neutral": 1, - "compose": { - "seq": { - "fn": "product" - }, - "and": { - "fn": "product" - }, - "xor": { - "fn": "weighted_sum", - "expr": "sum" - } - }, - "normalize": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - }, - "increasing_is_better": true - } - }, - "availability": { - "attribute_id": "availability", - "direction": "maximize", - "scale": "ratio", - "neutral": 1, - "compose": { - "seq": { - "fn": "product" - }, - "and": { - "fn": "product" - }, - "xor": { - "fn": "weighted_sum", - "expr": "sum" - } - }, - "normalize": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - }, - "increasing_is_better": true - } - }, - "security": { - "attribute_id": "security", - "direction": "maximize", - "scale": "ratio", - "neutral": 1, - "compose": { - "seq": { - "fn": "min" - }, - "and": { - "fn": "min" - }, - "xor": { - "fn": "weighted_sum", - "expr": "sum" - } - }, - "normalize": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - }, - "increasing_is_better": true - } - } - }, - "objective": { - "type": "weighted_sum", - "weights": { - "cost": 0.5, - "time": 0.5 - }, - "normalized": true - } - } -} \ No newline at end of file diff --git a/examples/minizinc-constrained-loop.json b/examples/minizinc-constrained-loop.json new file mode 100644 index 0000000..100e2db --- /dev/null +++ b/examples/minizinc-constrained-loop.json @@ -0,0 +1,114 @@ +{ + "tasks": [ + { + "id": "T1", + "name": "T1" + } + ], + "candidates": [ + { + "id": "C1", + "task_id": "T1", + "provider_id": "P1", + "features": { + "latency": 10 + }, + "name": "C1" + }, + { + "id": "C2", + "task_id": "T1", + "provider_id": "P2", + "features": { + "latency": 20 + }, + "name": "C2" + } + ], + "composition": { + "type": "structured", + "root": { + "kind": "LOOP", + "expected_iterations": 5, + "body": { + "kind": "TASK", + "task_id": "T1", + "id": "dd947628" + }, + "id": "26ac2fe7" + } + }, + "features": [ + { + "id": "latency", + "name": "latency", + "unit": "unit", + "scale": "ratio", + "valid_range": { + "min": 0, + "max": 10000 + }, + "direction": "minimize" + } + ], + "aggregation_policies": { + "latency": { + "compose": { + "seq": { + "fn": "sum" + }, + "and": { + "fn": "sum" + }, + "xor": { + "fn": "sum" + }, + "loop": { + "fn": "sum" + } + }, + "neutral": 0, + "normalize": { + "type": "minmax", + "bounds": { + "min": 0, + "max": 10000 + } + } + } + }, + "objective": { + "type": "weighted_sum", + "weights": { + "latency": 1.0 + }, + "normalized": true + }, + "constraints": [ + { + "kind": "attribute_bound", + "attribute_id": "latency", + "op": "<", + "value": 1200000000.0, + "scope": "global", + "id": "c0" + } + ], + "metadata": { + "id": "6a985cfe-53e7-4dc1-8c23-bb045f100081", + "name": "Instance 10: Constrained Loop", + "version": "1.0", + "created_at": "2026-01-26T12:00:00Z", + "description": "Instance 10: Constrained Loop" + }, + "providers": [ + { + "id": "P2", + "name": "Provider P2" + }, + { + "id": "P1", + "name": "Provider P1" + } + ] +} \ No newline at end of file diff --git a/examples/minizinc-huge.json b/examples/minizinc-huge.json new file mode 100644 index 0000000..3b8ecef --- /dev/null +++ b/examples/minizinc-huge.json @@ -0,0 +1,578 @@ +{ + "metadata": { + "id": "huge-minizinc", + "name": "Huge minizinc", + "version": "1.0", + "created_at": "2026-01-01T00:00:00Z" + }, + "tasks": [ + { + "id": "T1", + "name": "Task 1" + }, + { + "id": "T2", + "name": "Task 2" + }, + { + "id": "T3", + "name": "Task 3" + }, + { + "id": "T4", + "name": "Task 4" + }, + { + "id": "T5", + "name": "Task 5" + }, + { + "id": "T6", + "name": "Task 6" + }, + { + "id": "T7", + "name": "Task 7" + } + ], + "providers": [ + { + "id": "P1", + "name": "Provider 1" + }, + { + "id": "P2", + "name": "Provider 2" + }, + { + "id": "P3", + "name": "Provider 3" + } + ], + "candidates": [ + { + "id": "C1", + "task_id": "T1", + "provider_id": "P1", + "features": { + "cost": 10, + "latency": 5, + "reliability": 0.9, + "availability": 0.99, + "energy": 2 + } + }, + { + "id": "C2", + "task_id": "T1", + "provider_id": "P2", + "features": { + "cost": 20, + "latency": 10, + "reliability": 0.9, + "availability": 0.99, + "energy": 4 + } + }, + { + "id": "C3", + "task_id": "T1", + "provider_id": "P3", + "features": { + "cost": 30, + "latency": 15, + "reliability": 0.9, + "availability": 0.99, + "energy": 6 + } + }, + { + "id": "C4", + "task_id": "T2", + "provider_id": "P1", + "features": { + "cost": 40, + "latency": 20, + "reliability": 0.9, + "availability": 0.99, + "energy": 8 + } + }, + { + "id": "C5", + "task_id": "T2", + "provider_id": "P2", + "features": { + "cost": 50, + "latency": 25, + "reliability": 0.9, + "availability": 0.99, + "energy": 10 + } + }, + { + "id": "C6", + "task_id": "T2", + "provider_id": "P3", + "features": { + "cost": 60, + "latency": 30, + "reliability": 0.9, + "availability": 0.99, + "energy": 12 + } + }, + { + "id": "C7", + "task_id": "T3", + "provider_id": "P1", + "features": { + "cost": 70, + "latency": 35, + "reliability": 0.9, + "availability": 0.99, + "energy": 14 + } + }, + { + "id": "C8", + "task_id": "T3", + "provider_id": "P2", + "features": { + "cost": 80, + "latency": 40, + "reliability": 0.9, + "availability": 0.99, + "energy": 16 + } + }, + { + "id": "C9", + "task_id": "T3", + "provider_id": "P3", + "features": { + "cost": 90, + "latency": 45, + "reliability": 0.9, + "availability": 0.99, + "energy": 18 + } + }, + { + "id": "C10", + "task_id": "T4", + "provider_id": "P1", + "features": { + "cost": 100, + "latency": 50, + "reliability": 0.9, + "availability": 0.99, + "energy": 20 + } + }, + { + "id": "C11", + "task_id": "T4", + "provider_id": "P2", + "features": { + "cost": 110, + "latency": 55, + "reliability": 0.9, + "availability": 0.99, + "energy": 22 + } + }, + { + "id": "C12", + "task_id": "T4", + "provider_id": "P3", + "features": { + "cost": 120, + "latency": 60, + "reliability": 0.9, + "availability": 0.99, + "energy": 24 + } + }, + { + "id": "C13", + "task_id": "T5", + "provider_id": "P1", + "features": { + "cost": 130, + "latency": 65, + "reliability": 0.9, + "availability": 0.99, + "energy": 26 + } + }, + { + "id": "C14", + "task_id": "T5", + "provider_id": "P2", + "features": { + "cost": 140, + "latency": 70, + "reliability": 0.9, + "availability": 0.99, + "energy": 28 + } + }, + { + "id": "C15", + "task_id": "T5", + "provider_id": "P3", + "features": { + "cost": 150, + "latency": 75, + "reliability": 0.9, + "availability": 0.99, + "energy": 30 + } + }, + { + "id": "C16", + "task_id": "T6", + "provider_id": "P1", + "features": { + "cost": 160, + "latency": 80, + "reliability": 0.9, + "availability": 0.99, + "energy": 32 + } + }, + { + "id": "C17", + "task_id": "T6", + "provider_id": "P2", + "features": { + "cost": 170, + "latency": 85, + "reliability": 0.9, + "availability": 0.99, + "energy": 34 + } + }, + { + "id": "C18", + "task_id": "T6", + "provider_id": "P3", + "features": { + "cost": 180, + "latency": 90, + "reliability": 0.9, + "availability": 0.99, + "energy": 36 + } + }, + { + "id": "C19", + "task_id": "T7", + "provider_id": "P1", + "features": { + "cost": 190, + "latency": 95, + "reliability": 0.9, + "availability": 0.99, + "energy": 38 + } + }, + { + "id": "C20", + "task_id": "T7", + "provider_id": "P2", + "features": { + "cost": 200, + "latency": 100, + "reliability": 0.9, + "availability": 0.99, + "energy": 40 + } + }, + { + "id": "C21", + "task_id": "T7", + "provider_id": "P3", + "features": { + "cost": 210, + "latency": 105, + "reliability": 0.9, + "availability": 0.99, + "energy": 42 + } + } + ], + "features": [ + { + "id": "cost", + "name": "Cost", + "direction": "minimize", + "scale": "ratio", + "unit": "USD", + "valid_range": { + "min": 0, + "max": 100000 + } + }, + { + "id": "latency", + "name": "Latency", + "direction": "minimize", + "scale": "ratio", + "unit": "ms", + "valid_range": { + "min": 0, + "max": 10000 + } + }, + { + "id": "reliability", + "name": "Reliability", + "direction": "maximize", + "scale": "ratio", + "unit": "%", + "valid_range": { + "min": 0, + "max": 1 + } + }, + { + "id": "availability", + "name": "Availability", + "direction": "maximize", + "scale": "ratio", + "unit": "%", + "valid_range": { + "min": 0, + "max": 1 + } + }, + { + "id": "energy", + "name": "Energy", + "direction": "minimize", + "scale": "ratio", + "unit": "J", + "valid_range": { + "min": 0, + "max": 10000 + } + } + ], + "composition": { + "type": "structured", + "root": { + "id": "root_seq", + "kind": "SEQ", + "children": [ + { + "id": "n1", + "kind": "TASK", + "task_id": "T1" + }, + { + "id": "n2_and", + "kind": "AND", + "children": [ + { + "id": "n2a", + "kind": "TASK", + "task_id": "T2" + }, + { + "id": "n2b", + "kind": "TASK", + "task_id": "T3" + } + ] + }, + { + "id": "n3_xor", + "kind": "XOR", + "branches": [ + { + "p": 0.5, + "child": { + "id": "n3a", + "kind": "TASK", + "task_id": "T4" + } + }, + { + "p": 0.5, + "child": { + "id": "n3b", + "kind": "TASK", + "task_id": "T5" + } + } + ] + }, + { + "id": "n4_loop", + "kind": "LOOP", + "expected_iterations": 3, + "body": { + "id": "n4_inner_seq", + "kind": "SEQ", + "children": [ + { + "id": "n4a", + "kind": "TASK", + "task_id": "T6" + }, + { + "id": "n4b", + "kind": "TASK", + "task_id": "T7" + } + ] + } + } + ] + } + }, + "aggregation_policies": { + "cost": { + "neutral": 0, + "normalize": { + "type": "identity" + }, + "compose": { + "seq": { + "fn": "sum" + }, + "and": { + "fn": "max" + }, + "xor": { + "fn": "weighted_sum", + "expr": "sum(w * x)" + }, + "loop": { + "fn": "sum" + } + } + }, + "latency": { + "neutral": 0, + "normalize": { + "type": "identity" + }, + "compose": { + "seq": { + "fn": "sum" + }, + "and": { + "fn": "max" + }, + "xor": { + "fn": "weighted_sum", + "expr": "sum(w * x)" + }, + "loop": { + "fn": "sum" + } + } + }, + "reliability": { + "neutral": 1, + "normalize": { + "type": "identity" + }, + "compose": { + "seq": { + "fn": "sum" + }, + "and": { + "fn": "max" + }, + "xor": { + "fn": "weighted_sum", + "expr": "sum(w * x)" + }, + "loop": { + "fn": "sum" + } + } + }, + "availability": { + "neutral": 1, + "normalize": { + "type": "identity" + }, + "compose": { + "seq": { + "fn": "sum" + }, + "and": { + "fn": "max" + }, + "xor": { + "fn": "weighted_sum", + "expr": "sum(w * x)" + }, + "loop": { + "fn": "sum" + } + } + }, + "energy": { + "neutral": 0, + "normalize": { + "type": "identity" + }, + "compose": { + "seq": { + "fn": "sum" + }, + "and": { + "fn": "max" + }, + "xor": { + "fn": "weighted_sum", + "expr": "sum(w * x)" + }, + "loop": { + "fn": "sum" + } + } + } + }, + "objective": { + "type": "weighted_sum", + "weights": { + "cost": 1 + }, + "normalized": true + }, + "constraints": [ + { + "id": "c0", + "kind": "attribute_bound", + "scope": "global", + "attribute_id": "cost", + "op": "<=", + "value": 10000 + }, + { + "id": "c1", + "kind": "attribute_bound", + "scope": "local", + "task_id": "T1", + "attribute_id": "latency", + "op": "<=", + "value": 100 + }, + { + "id": "c2", + "kind": "dependency", + "type": "same_provider", + "tasks": [ + "T2", + "T3" + ] + } + ] +} \ No newline at end of file diff --git a/examples/minizinc-tight.json b/examples/minizinc-tight.json new file mode 100644 index 0000000..fd1957f --- /dev/null +++ b/examples/minizinc-tight.json @@ -0,0 +1,111 @@ +{ + "tasks": [ + { + "id": "T1", + "name": "T1" + }, + { + "id": "T2", + "name": "T2" + } + ], + "candidates": [ + { + "id": "C1", + "task_id": "T1", + "provider_id": "P1", + "features": { + "cost": 50 + }, + "name": "C1" + }, + { + "id": "C2", + "task_id": "T2", + "provider_id": "P1", + "features": { + "cost": 50 + }, + "name": "C2" + } + ], + "composition": { + "type": "structured", + "root": { + "kind": "SEQ", + "children": [ + { + "kind": "TASK", + "task_id": "T1", + "id": "086e5425" + }, + { + "kind": "TASK", + "task_id": "T2", + "id": "2b1d3b1c" + } + ], + "id": "41e13951" + } + }, + "features": [ + { + "id": "cost", + "name": "cost", + "unit": "unit", + "scale": "ratio", + "valid_range": { + "min": 0, + "max": 10000 + }, + "direction": "minimize" + } + ], + "aggregation_policies": { + "cost": { + "compose": { + "seq": { + "fn": "sum" + }, + "and": { + "fn": "sum" + }, + "xor": { + "fn": "sum" + }, + "loop": { + "fn": "sum" + } + }, + "neutral": 0, + "normalize": { + "type": "minmax", + "bounds": { + "min": 0, + "max": 10000 + } + } + } + }, + "objective": { + "type": "weighted_sum", + "weights": { + "cost": 1.0 + }, + "normalized": true + }, + "constraints": [], + "metadata": { + "id": "9abb4800-0925-47bf-8b39-7fe20cbff8aa", + "name": "Instance 9: Tight Constraints", + "version": "1.0", + "created_at": "2026-01-26T12:00:00Z", + "description": "Instance 9: Tight Constraints" + }, + "providers": [ + { + "id": "P1", + "name": "Provider P1" + } + ] +} \ No newline at end of file diff --git a/examples/random-search-example.json b/examples/random-search-example.json new file mode 100644 index 0000000..6a413da --- /dev/null +++ b/examples/random-search-example.json @@ -0,0 +1,216 @@ +{ + "metadata": { + "id": "many-obj-complex-example", + "name": "Many-OBJ Complex Workflow", + "version": "1.0", + "created_at": "2024-01-20T12:00:00Z" + }, + "features": [ + { + "id": "cost", + "name": "Cost", + "direction": "minimize", + "unit": "USD", + "scale": "ratio", + "valid_range": { + "min": 0, + "max": 1000 + } + }, + { + "id": "time", + "name": "Response Time", + "direction": "minimize", + "unit": "ms", + "scale": "ratio", + "valid_range": { + "min": 0, + "max": 5000 + } + } + ], + "tasks": [ + { + "id": "payment", + "name": "Payment Processing" + }, + { + "id": "inventory", + "name": "Inventory Check" + }, + { + "id": "shipping", + "name": "Shipping Logistics" + } + ], + "providers": [ + { + "id": "stripe", + "name": "Stripe" + }, + { + "id": "paypal", + "name": "PayPal" + }, + { + "id": "aws", + "name": "AWS Warehouse" + }, + { + "id": "azure", + "name": "Azure Inventory" + }, + { + "id": "fedex", + "name": "FedEx" + }, + { + "id": "ups", + "name": "UPS" + } + ], + "candidates": [ + { + "id": "pay_stripe", + "task_id": "payment", + "provider_id": "stripe", + "features": { + "cost": 0.3, + "time": 200 + } + }, + { + "id": "pay_paypal", + "task_id": "payment", + "provider_id": "paypal", + "features": { + "cost": 0.5, + "time": 150 + } + }, + { + "id": "inv_aws", + "task_id": "inventory", + "provider_id": "aws", + "features": { + "cost": 0.1, + "time": 50 + } + }, + { + "id": "inv_azure", + "task_id": "inventory", + "provider_id": "azure", + "features": { + "cost": 0.05, + "time": 80 + } + }, + { + "id": "ship_fedex", + "task_id": "shipping", + "provider_id": "fedex", + "features": { + "cost": 5.0, + "time": 1000 + } + }, + { + "id": "ship_ups", + "task_id": "shipping", + "provider_id": "ups", + "features": { + "cost": 4.5, + "time": 1200 + } + } + ], + "composition": { + "type": "structured", + "root": { + "id": "root_seq", + "kind": "SEQ", + "children": [ + { + "id": "task_payment", + "kind": "TASK", + "task_id": "payment" + }, + { + "id": "par_flow", + "kind": "AND", + "children": [ + { + "id": "task_inv", + "kind": "TASK", + "task_id": "inventory" + }, + { + "id": "task_ship", + "kind": "TASK", + "task_id": "shipping" + } + ] + } + ] + } + }, + "aggregation_policies": { + "cost": { + "neutral": 0, + "compose": { + "seq": { + "fn": "sum" + }, + "and": { + "fn": "sum" + }, + "xor": { + "fn": "sum" + }, + "loop": { + "fn": "sum" + } + }, + "normalize": { + "type": "minmax", + "bounds": { + "min": 0, + "max": 1000 + } + } + }, + "time": { + "neutral": 0, + "compose": { + "seq": { + "fn": "sum" + }, + "and": { + "fn": "max" + }, + "xor": { + "fn": "sum" + }, + "loop": { + "fn": "sum" + } + }, + "normalize": { + "type": "minmax", + "bounds": { + "min": 0, + "max": 5000 + } + } + } + }, + "objective": { + "type": "weighted_sum", + "weights": { + "cost": 0.5, + "time": 0.5 + }, + "normalized": true + } +} \ No newline at end of file diff --git a/examples/random-search-huge.json b/examples/random-search-huge.json new file mode 100644 index 0000000..2be9fb9 --- /dev/null +++ b/examples/random-search-huge.json @@ -0,0 +1,560 @@ +{ + "metadata": { + "id": "huge-random", + "name": "Huge random", + "version": "1.0", + "created_at": "2026-01-01T00:00:00Z" + }, + "tasks": [ + { + "id": "T1", + "name": "Task 1" + }, + { + "id": "T2", + "name": "Task 2" + }, + { + "id": "T3", + "name": "Task 3" + }, + { + "id": "T4", + "name": "Task 4" + }, + { + "id": "T5", + "name": "Task 5" + }, + { + "id": "T6", + "name": "Task 6" + }, + { + "id": "T7", + "name": "Task 7" + } + ], + "providers": [ + { + "id": "P1", + "name": "Provider 1" + }, + { + "id": "P2", + "name": "Provider 2" + }, + { + "id": "P3", + "name": "Provider 3" + } + ], + "candidates": [ + { + "id": "C1", + "task_id": "T1", + "provider_id": "P1", + "features": { + "cost": 10, + "latency": 5, + "reliability": 0.9, + "availability": 0.99, + "energy": 2 + } + }, + { + "id": "C2", + "task_id": "T1", + "provider_id": "P2", + "features": { + "cost": 20, + "latency": 10, + "reliability": 0.9, + "availability": 0.99, + "energy": 4 + } + }, + { + "id": "C3", + "task_id": "T1", + "provider_id": "P3", + "features": { + "cost": 30, + "latency": 15, + "reliability": 0.9, + "availability": 0.99, + "energy": 6 + } + }, + { + "id": "C4", + "task_id": "T2", + "provider_id": "P1", + "features": { + "cost": 40, + "latency": 20, + "reliability": 0.9, + "availability": 0.99, + "energy": 8 + } + }, + { + "id": "C5", + "task_id": "T2", + "provider_id": "P2", + "features": { + "cost": 50, + "latency": 25, + "reliability": 0.9, + "availability": 0.99, + "energy": 10 + } + }, + { + "id": "C6", + "task_id": "T2", + "provider_id": "P3", + "features": { + "cost": 60, + "latency": 30, + "reliability": 0.9, + "availability": 0.99, + "energy": 12 + } + }, + { + "id": "C7", + "task_id": "T3", + "provider_id": "P1", + "features": { + "cost": 70, + "latency": 35, + "reliability": 0.9, + "availability": 0.99, + "energy": 14 + } + }, + { + "id": "C8", + "task_id": "T3", + "provider_id": "P2", + "features": { + "cost": 80, + "latency": 40, + "reliability": 0.9, + "availability": 0.99, + "energy": 16 + } + }, + { + "id": "C9", + "task_id": "T3", + "provider_id": "P3", + "features": { + "cost": 90, + "latency": 45, + "reliability": 0.9, + "availability": 0.99, + "energy": 18 + } + }, + { + "id": "C10", + "task_id": "T4", + "provider_id": "P1", + "features": { + "cost": 100, + "latency": 50, + "reliability": 0.9, + "availability": 0.99, + "energy": 20 + } + }, + { + "id": "C11", + "task_id": "T4", + "provider_id": "P2", + "features": { + "cost": 110, + "latency": 55, + "reliability": 0.9, + "availability": 0.99, + "energy": 22 + } + }, + { + "id": "C12", + "task_id": "T4", + "provider_id": "P3", + "features": { + "cost": 120, + "latency": 60, + "reliability": 0.9, + "availability": 0.99, + "energy": 24 + } + }, + { + "id": "C13", + "task_id": "T5", + "provider_id": "P1", + "features": { + "cost": 130, + "latency": 65, + "reliability": 0.9, + "availability": 0.99, + "energy": 26 + } + }, + { + "id": "C14", + "task_id": "T5", + "provider_id": "P2", + "features": { + "cost": 140, + "latency": 70, + "reliability": 0.9, + "availability": 0.99, + "energy": 28 + } + }, + { + "id": "C15", + "task_id": "T5", + "provider_id": "P3", + "features": { + "cost": 150, + "latency": 75, + "reliability": 0.9, + "availability": 0.99, + "energy": 30 + } + }, + { + "id": "C16", + "task_id": "T6", + "provider_id": "P1", + "features": { + "cost": 160, + "latency": 80, + "reliability": 0.9, + "availability": 0.99, + "energy": 32 + } + }, + { + "id": "C17", + "task_id": "T6", + "provider_id": "P2", + "features": { + "cost": 170, + "latency": 85, + "reliability": 0.9, + "availability": 0.99, + "energy": 34 + } + }, + { + "id": "C18", + "task_id": "T6", + "provider_id": "P3", + "features": { + "cost": 180, + "latency": 90, + "reliability": 0.9, + "availability": 0.99, + "energy": 36 + } + }, + { + "id": "C19", + "task_id": "T7", + "provider_id": "P1", + "features": { + "cost": 190, + "latency": 95, + "reliability": 0.9, + "availability": 0.99, + "energy": 38 + } + }, + { + "id": "C20", + "task_id": "T7", + "provider_id": "P2", + "features": { + "cost": 200, + "latency": 100, + "reliability": 0.9, + "availability": 0.99, + "energy": 40 + } + }, + { + "id": "C21", + "task_id": "T7", + "provider_id": "P3", + "features": { + "cost": 210, + "latency": 105, + "reliability": 0.9, + "availability": 0.99, + "energy": 42 + } + } + ], + "features": [ + { + "id": "cost", + "name": "Cost", + "direction": "minimize", + "scale": "ratio", + "unit": "USD", + "valid_range": { + "min": 0, + "max": 100000 + } + }, + { + "id": "latency", + "name": "Latency", + "direction": "minimize", + "scale": "ratio", + "unit": "ms", + "valid_range": { + "min": 0, + "max": 10000 + } + }, + { + "id": "reliability", + "name": "Reliability", + "direction": "maximize", + "scale": "ratio", + "unit": "%", + "valid_range": { + "min": 0, + "max": 1 + } + }, + { + "id": "availability", + "name": "Availability", + "direction": "maximize", + "scale": "ratio", + "unit": "%", + "valid_range": { + "min": 0, + "max": 1 + } + }, + { + "id": "energy", + "name": "Energy", + "direction": "minimize", + "scale": "ratio", + "unit": "J", + "valid_range": { + "min": 0, + "max": 10000 + } + } + ], + "composition": { + "type": "structured", + "root": { + "id": "root_seq", + "kind": "SEQ", + "children": [ + { + "id": "n1", + "kind": "TASK", + "task_id": "T1" + }, + { + "id": "n2_and", + "kind": "AND", + "children": [ + { + "id": "n2a", + "kind": "TASK", + "task_id": "T2" + }, + { + "id": "n2b", + "kind": "TASK", + "task_id": "T3" + } + ] + }, + { + "id": "n3_xor", + "kind": "XOR", + "branches": [ + { + "p": 0.5, + "child": { + "id": "n3a", + "kind": "TASK", + "task_id": "T4" + } + }, + { + "p": 0.5, + "child": { + "id": "n3b", + "kind": "TASK", + "task_id": "T5" + } + } + ] + }, + { + "id": "n4_loop", + "kind": "LOOP", + "expected_iterations": 3, + "body": { + "id": "n4_inner_seq", + "kind": "SEQ", + "children": [ + { + "id": "n4a", + "kind": "TASK", + "task_id": "T6" + }, + { + "id": "n4b", + "kind": "TASK", + "task_id": "T7" + } + ] + } + } + ] + } + }, + "aggregation_policies": { + "cost": { + "neutral": 0, + "normalize": { + "type": "identity" + }, + "compose": { + "seq": { + "fn": "sum" + }, + "and": { + "fn": "max" + }, + "xor": { + "fn": "weighted_sum", + "expr": "sum(w * x)" + }, + "loop": { + "fn": "sum" + } + } + }, + "latency": { + "neutral": 0, + "normalize": { + "type": "identity" + }, + "compose": { + "seq": { + "fn": "sum" + }, + "and": { + "fn": "max" + }, + "xor": { + "fn": "weighted_sum", + "expr": "sum(w * x)" + }, + "loop": { + "fn": "sum" + } + } + }, + "reliability": { + "neutral": 1, + "normalize": { + "type": "identity" + }, + "compose": { + "seq": { + "fn": "sum" + }, + "and": { + "fn": "max" + }, + "xor": { + "fn": "weighted_sum", + "expr": "sum(w * x)" + }, + "loop": { + "fn": "sum" + } + } + }, + "availability": { + "neutral": 1, + "normalize": { + "type": "identity" + }, + "compose": { + "seq": { + "fn": "sum" + }, + "and": { + "fn": "max" + }, + "xor": { + "fn": "weighted_sum", + "expr": "sum(w * x)" + }, + "loop": { + "fn": "sum" + } + } + }, + "energy": { + "neutral": 0, + "normalize": { + "type": "identity" + }, + "compose": { + "seq": { + "fn": "sum" + }, + "and": { + "fn": "max" + }, + "xor": { + "fn": "weighted_sum", + "expr": "sum(w * x)" + }, + "loop": { + "fn": "sum" + } + } + } + }, + "objective": { + "type": "weighted_sum", + "weights": { + "cost": 1 + }, + "normalized": true + }, + "constraints": [ + { + "id": "c0", + "kind": "attribute_bound", + "scope": "global", + "attribute_id": "cost", + "op": "<=", + "value": 10000 + } + ] +} \ No newline at end of file diff --git a/examples/random-search-valid.json b/examples/random-search-valid.json new file mode 100644 index 0000000..f3d6271 --- /dev/null +++ b/examples/random-search-valid.json @@ -0,0 +1,167 @@ +{ + "metadata": { + "id": "test-many-obj-01", + "name": "Many-OBJ Test Instance", + "version": "1.0", + "created_at": "2023-10-27T10:00:00Z" + }, + "providers": [ + { + "id": "p1", + "name": "Provider 1" + }, + { + "id": "p2", + "name": "Provider 2" + } + ], + "tasks": [ + { + "id": "t1", + "name": "Task A" + }, + { + "id": "t2", + "name": "Task B" + } + ], + "composition": { + "type": "structured", + "root": { + "id": "n1", + "kind": "SEQ", + "children": [ + { + "id": "t1", + "kind": "TASK", + "task_id": "t1" + }, + { + "id": "t2", + "kind": "TASK", + "task_id": "t2" + } + ] + } + }, + "candidates": [ + { + "id": "s1", + "task_id": "t1", + "provider_id": "p1", + "features": { + "cost": 10.0, + "time": 50.0 + } + }, + { + "id": "s2", + "task_id": "t1", + "provider_id": "p2", + "features": { + "cost": 12.0, + "time": 45.0 + } + }, + { + "id": "s3", + "task_id": "t2", + "provider_id": "p1", + "features": { + "cost": 5.0, + "time": 20.0 + } + }, + { + "id": "s4", + "task_id": "t2", + "provider_id": "p2", + "features": { + "cost": 8.0, + "time": 15.0 + } + } + ], + "features": [ + { + "id": "cost", + "name": "Cost", + "direction": "minimize", + "unit": "USD", + "scale": "ratio", + "valid_range": { + "min": 0, + "max": 1000 + } + }, + { + "id": "time", + "name": "Response Time", + "direction": "minimize", + "unit": "ms", + "scale": "ratio", + "valid_range": { + "min": 0, + "max": 5000 + } + } + ], + "objective": { + "type": "weighted_sum", + "weights": { + "cost": 0.5, + "time": 0.5 + }, + "normalized": true + }, + "aggregation_policies": { + "cost": { + "neutral": 0, + "normalize": { + "type": "minmax", + "bounds": { + "min": 0, + "max": 100 + } + }, + "compose": { + "seq": { + "fn": "sum" + }, + "and": { + "fn": "max" + }, + "xor": { + "fn": "sum" + }, + "loop": { + "fn": "sum" + } + } + }, + "time": { + "neutral": 0, + "normalize": { + "type": "minmax", + "bounds": { + "min": 0, + "max": 1000 + } + }, + "compose": { + "seq": { + "fn": "sum" + }, + "and": { + "fn": "max" + }, + "xor": { + "fn": "sum" + }, + "loop": { + "fn": "sum" + } + } + } + } +} \ No newline at end of file diff --git a/examples/structured-seq-andpar-xor.json b/examples/structured-seq-andpar-xor.json deleted file mode 100644 index 0f788aa..0000000 --- a/examples/structured-seq-andpar-xor.json +++ /dev/null @@ -1,351 +0,0 @@ -{ - "engine_id": "minizinc-csp", - "instance": { - "metadata": { - "id": "ex-002", - "name": "Complex Structure", - "version": "1.0", - "created_at": "2024-01-01T00:00:00Z" - }, - "providers": [ - { - "id": "p1", - "name": "Provider 1" - } - ], - "tasks": [ - { - "id": "t1", - "name": "Auth" - }, - { - "id": "t2", - "name": "Process" - }, - { - "id": "t3", - "name": "Log" - } - ], - "candidates": [ - { - "id": "c1a", - "task_id": "t1", - "provider_id": "p1", - "qos": { - "cost": 5, - "time": 20 - } - }, - { - "id": "c1b", - "task_id": "t1", - "provider_id": "p1", - "qos": { - "cost": 2, - "time": 50 - } - }, - { - "id": "c2a", - "task_id": "t2", - "provider_id": "p1", - "qos": { - "cost": 10, - "time": 100 - } - }, - { - "id": "c2b", - "task_id": "t2", - "provider_id": "p1", - "qos": { - "cost": 20, - "time": 60 - } - }, - { - "id": "c3a", - "task_id": "t3", - "provider_id": "p1", - "qos": { - "cost": 1, - "time": 10 - } - } - ], - "features": [ - { - "id": "cost", - "name": "Cost", - "direction": "minimize", - "unit": "USD", - "scale": "ratio", - "valid_range": { - "min": 0, - "max": 10000 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 100 - }, - "increasing_is_better": false - } - }, - { - "id": "time", - "name": "Time", - "direction": "minimize", - "unit": "ms", - "scale": "ratio", - "valid_range": { - "min": 0, - "max": 10000 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 100 - }, - "increasing_is_better": false - } - }, - { - "id": "reliability", - "name": "Reliability", - "direction": "maximize", - "unit": "", - "scale": "ratio", - "valid_range": { - "min": 0, - "max": 1 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - }, - "increasing_is_better": true - } - }, - { - "id": "availability", - "name": "Availability", - "direction": "maximize", - "unit": "", - "scale": "ratio", - "valid_range": { - "min": 0, - "max": 1 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - }, - "increasing_is_better": true - } - }, - { - "id": "security", - "name": "Security", - "direction": "maximize", - "unit": "", - "scale": "ratio", - "valid_range": { - "min": 0, - "max": 1 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - }, - "increasing_is_better": true - } - } - ], - "composition": { - "type": "structured", - "root": { - "id": "n1", - "kind": "SEQ", - "children": [ - { - "id": "n2", - "kind": "TASK", - "task_id": "t1" - }, - { - "id": "n3", - "kind": "XOR", - "branches": [ - { - "p": 0.8, - "child": { - "id": "n4", - "kind": "TASK", - "task_id": "t2" - } - }, - { - "p": 0.2, - "child": { - "id": "n5", - "kind": "TASK", - "task_id": "t3" - } - } - ] - } - ] - } - }, - "aggregation_policies": { - "cost": { - "attribute_id": "cost", - "direction": "minimize", - "scale": "ratio", - "neutral": 0, - "compose": { - "seq": { - "fn": "sum" - }, - "and": { - "fn": "sum" - }, - "xor": { - "fn": "weighted_sum", - "expr": "sum" - } - }, - "normalize": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - }, - "increasing_is_better": false - } - }, - "time": { - "attribute_id": "time", - "direction": "minimize", - "scale": "ratio", - "neutral": 0, - "compose": { - "seq": { - "fn": "sum" - }, - "and": { - "fn": "max" - }, - "xor": { - "fn": "weighted_sum", - "expr": "sum" - } - }, - "normalize": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - }, - "increasing_is_better": false - } - }, - "reliability": { - "attribute_id": "reliability", - "direction": "maximize", - "scale": "ratio", - "neutral": 1, - "compose": { - "seq": { - "fn": "product" - }, - "and": { - "fn": "product" - }, - "xor": { - "fn": "weighted_sum", - "expr": "sum" - } - }, - "normalize": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - }, - "increasing_is_better": true - } - }, - "availability": { - "attribute_id": "availability", - "direction": "maximize", - "scale": "ratio", - "neutral": 1, - "compose": { - "seq": { - "fn": "product" - }, - "and": { - "fn": "product" - }, - "xor": { - "fn": "weighted_sum", - "expr": "sum" - } - }, - "normalize": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - }, - "increasing_is_better": true - } - }, - "security": { - "attribute_id": "security", - "direction": "maximize", - "scale": "ratio", - "neutral": 1, - "compose": { - "seq": { - "fn": "min" - }, - "and": { - "fn": "min" - }, - "xor": { - "fn": "weighted_sum", - "expr": "sum" - } - }, - "normalize": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - }, - "increasing_is_better": true - } - } - }, - "objective": { - "type": "weighted_sum", - "weights": { - "cost": 0.8, - "time": 0.2 - }, - "normalized": true - } - } -} \ No newline at end of file diff --git a/examples/validation-engine-disconnected.json b/examples/validation-engine-disconnected.json deleted file mode 100644 index 8bfb440..0000000 --- a/examples/validation-engine-disconnected.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "engine_id": "minizinc-csp", - "instance": { - "metadata": { - "id": "test-disconnected", - "name": "Disconnected Task Test", - "version": "1.0", - "created_at": "2024-01-01T00:00:00Z" - }, - "providers": [ - { - "id": "p1", - "name": "P1" - } - ], - "tasks": [ - { - "id": "t1", - "name": "Task 1" - }, - { - "id": "t2", - "name": "Task 2 Unused" - } - ], - "candidates": [ - { - "id": "c1", - "task_id": "t1", - "provider_id": "p1", - "qos": { - "cost": 1 - } - } - ], - "features": [ - { - "id": "cost", - "name": "Cost", - "direction": "minimize", - "unit": "USD", - "scale": "ratio", - "valid_range": { - "min": 0, - "max": 100 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 20 - } - } - } - ], - "composition": { - "type": "structured", - "root": { - "id": "n1", - "kind": "TASK", - "task_id": "t1" - } - }, - "aggregation_policies": {}, - "objective": { - "type": "weighted_sum", - "weights": { - "cost": 1.0 - }, - "normalized": true - } - } -} \ No newline at end of file diff --git a/examples/validation-gateway-duplicate.json b/examples/validation-gateway-duplicate.json deleted file mode 100644 index 49dc7cc..0000000 --- a/examples/validation-gateway-duplicate.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "engine_id": "minizinc-csp", - "instance": { - "metadata": { - "id": "test-dup", - "name": "Duplicate ID Test", - "version": "1.0", - "created_at": "2024-01-01T00:00:00Z" - }, - "providers": [ - { - "id": "p1", - "name": "P1" - } - ], - "tasks": [ - { - "id": "t1", - "name": "Task 1" - }, - { - "id": "t1", - "name": "Task 1 Duplicate" - } - ], - "candidates": [ - { - "id": "c1", - "task_id": "t1", - "provider_id": "p1", - "qos": { - "cost": 1 - } - } - ], - "features": [ - { - "id": "cost", - "name": "Cost", - "direction": "minimize", - "unit": "USD", - "scale": "ratio", - "valid_range": { - "min": 0, - "max": 100 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 20 - } - } - } - ], - "composition": { - "type": "structured", - "root": { - "id": "n1", - "kind": "TASK", - "task_id": "t1" - } - }, - "aggregation_policies": {}, - "objective": { - "type": "weighted_sum", - "weights": { - "cost": 1.0 - }, - "normalized": true - } - } -} \ No newline at end of file diff --git a/examples/validation-gateway-specialization.json b/examples/validation-gateway-specialization.json deleted file mode 100644 index 086ff9e..0000000 --- a/examples/validation-gateway-specialization.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "engine_id": "minizinc-csp", - "instance": { - "metadata": { "id": "test-spec-fail", "name": "Spec Fail", "version": "1.0", "created_at": "2024-01-01T00:00:00Z" }, - "providers": [{"id": "p1", "name": "P1"}], - "tasks": [ {"id": "t1", "name": "Task 1"} ], - "candidates": [ { "id": "c1", "task_id": "t1", "provider_id": "p1", "qos": { "cost": 1 } } ], - "features": [ - { "id": "cost", "name": "Cost", "direction": "minimize", "unit": "USD", "scale": "ratio", "valid_range": {"min": 0, "max": 100}, "normalization": { "type": "minmax", "bounds": { "min": 0, "max": 20 } } } - ], - "composition": { - "type": "dag", - "nodes": [ { "id": "n1", "kind": "TASK", "task_id": "t1" } ], - "edges": [] - }, - "aggregation_policies": {}, - "objective": { "type": "weighted_sum", "weights": { "cost": 1.0 }, "normalized": true } - } -} diff --git a/examples/variation-objective-cost.json b/examples/variation-objective-cost.json deleted file mode 100644 index 1c9ecde..0000000 --- a/examples/variation-objective-cost.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "engine_id": "minizinc-csp", - "instance": { - "metadata": { - "id": "var-cost-opt", - "name": "Variation: Cost Optimization", - "version": "1.0", - "created_at": "2024-01-01T00:00:00Z" - }, - "providers": [ - { - "id": "p1", - "name": "Provider 1" - } - ], - "tasks": [ - { - "id": "t1", - "name": "Task 1" - }, - { - "id": "t2", - "name": "Task 2" - } - ], - "candidates": [ - { - "id": "c1_slow_cheap", - "task_id": "t1", - "provider_id": "p1", - "qos": { - "cost": 1, - "time": 100, - "reliability": 0.9, - "availability": 0.9, - "security": 0.9 - } - }, - { - "id": "c1_fast_expensive", - "task_id": "t1", - "provider_id": "p1", - "qos": { - "cost": 100, - "time": 10, - "reliability": 0.9, - "availability": 0.9, - "security": 0.9 - } - }, - { - "id": "c2_slow_cheap", - "task_id": "t2", - "provider_id": "p1", - "qos": { - "cost": 1, - "time": 100, - "reliability": 0.9, - "availability": 0.9, - "security": 0.9 - } - }, - { - "id": "c2_fast_expensive", - "task_id": "t2", - "provider_id": "p1", - "qos": { - "cost": 100, - "time": 10, - "reliability": 0.9, - "availability": 0.9, - "security": 0.9 - } - } - ], - "features": [ - { - "id": "cost", - "name": "Cost", - "direction": "minimize", - "unit": "USD", - "scale": "ratio", - "valid_range": { - "min": 0, - "max": 1000 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 200 - } - } - }, - { - "id": "time", - "name": "Time", - "direction": "minimize", - "unit": "ms", - "scale": "ratio", - "valid_range": { - "min": 0, - "max": 1000 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 200 - } - } - }, - { - "id": "reliability", - "name": "Rel", - "direction": "maximize", - "unit": "prob", - "scale": "ratio", - "valid_range": { - "min": 0, - "max": 1 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - } - } - }, - { - "id": "availability", - "name": "Avail", - "direction": "maximize", - "unit": "prob", - "scale": "ratio", - "valid_range": { - "min": 0, - "max": 1 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - } - } - }, - { - "id": "security", - "name": "Sec", - "direction": "maximize", - "unit": "prob", - "scale": "ratio", - "valid_range": { - "min": 0, - "max": 1 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - } - } - } - ], - "composition": { - "type": "structured", - "root": { - "id": "root", - "kind": "SEQ", - "children": [ - { - "id": "n1", - "kind": "TASK", - "task_id": "t1" - }, - { - "id": "n2", - "kind": "TASK", - "task_id": "t2" - } - ] - } - }, - "aggregation_policies": {}, - "objective": { - "type": "weighted_sum", - "weights": { - "cost": 0.9, - "time": 0.1 - }, - "normalized": true - } - } -} \ No newline at end of file diff --git a/examples/variation-objective-time.json b/examples/variation-objective-time.json deleted file mode 100644 index 1c17b26..0000000 --- a/examples/variation-objective-time.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "engine_id": "minizinc-csp", - "instance": { - "metadata": { - "id": "var-time-opt", - "name": "Variation: Time Optimization", - "version": "1.0", - "created_at": "2024-01-01T00:00:00Z" - }, - "providers": [ - { - "id": "p1", - "name": "Provider 1" - } - ], - "tasks": [ - { - "id": "t1", - "name": "Task 1" - }, - { - "id": "t2", - "name": "Task 2" - } - ], - "candidates": [ - { - "id": "c1_slow_cheap", - "task_id": "t1", - "provider_id": "p1", - "qos": { - "cost": 1, - "time": 100, - "reliability": 0.9, - "availability": 0.9, - "security": 0.9 - } - }, - { - "id": "c1_fast_expensive", - "task_id": "t1", - "provider_id": "p1", - "qos": { - "cost": 100, - "time": 10, - "reliability": 0.9, - "availability": 0.9, - "security": 0.9 - } - }, - { - "id": "c2_slow_cheap", - "task_id": "t2", - "provider_id": "p1", - "qos": { - "cost": 1, - "time": 100, - "reliability": 0.9, - "availability": 0.9, - "security": 0.9 - } - }, - { - "id": "c2_fast_expensive", - "task_id": "t2", - "provider_id": "p1", - "qos": { - "cost": 100, - "time": 10, - "reliability": 0.9, - "availability": 0.9, - "security": 0.9 - } - } - ], - "features": [ - { - "id": "cost", - "name": "Cost", - "direction": "minimize", - "unit": "USD", - "scale": "ratio", - "valid_range": { - "min": 0, - "max": 1000 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 200 - } - } - }, - { - "id": "time", - "name": "Time", - "direction": "minimize", - "unit": "ms", - "scale": "ratio", - "valid_range": { - "min": 0, - "max": 1000 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 200 - } - } - }, - { - "id": "reliability", - "name": "Rel", - "direction": "maximize", - "unit": "prob", - "scale": "ratio", - "valid_range": { - "min": 0, - "max": 1 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - } - } - }, - { - "id": "availability", - "name": "Avail", - "direction": "maximize", - "unit": "prob", - "scale": "ratio", - "valid_range": { - "min": 0, - "max": 1 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - } - } - }, - { - "id": "security", - "name": "Sec", - "direction": "maximize", - "unit": "prob", - "scale": "ratio", - "valid_range": { - "min": 0, - "max": 1 - }, - "normalization": { - "type": "minmax", - "bounds": { - "min": 0, - "max": 1 - } - } - } - ], - "composition": { - "type": "structured", - "root": { - "id": "root", - "kind": "SEQ", - "children": [ - { - "id": "n1", - "kind": "TASK", - "task_id": "t1" - }, - { - "id": "n2", - "kind": "TASK", - "task_id": "t2" - } - ] - } - }, - "aggregation_policies": {}, - "objective": { - "type": "weighted_sum", - "weights": { - "cost": 0.1, - "time": 0.9 - }, - "normalized": true - } - } -} \ No newline at end of file diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 0000000..b9edebc --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1,2 @@ +# OpenBinding Gateway API Base URL +VITE_API_BASE_URL=http://localhost:8000 diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 4568041..58a40ed 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -7,14 +7,22 @@ RUN corepack enable WORKDIR /app -COPY package.json . -# COPY pnpm-lock.yaml . # If we had one, but we just installed -RUN pnpm install +COPY package.json pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile COPY . . + +# Set default API URL for Docker deployment (can be overridden) +ENV VITE_API_BASE_URL=http://localhost:8000 + RUN pnpm run build FROM nginx:alpine COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Create examples directory and make it available to be mounted +RUN mkdir -p /usr/share/nginx/html/examples + EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/index.html b/frontend/index.html index 072a57e..74edcae 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,9 +2,10 @@ - + - frontend + + OpenBinding - QoS-Aware Service Composition
diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..c361672 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,28 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json application/javascript; + + # SPA routing - serve index.html for all routes + location / { + try_files $uri $uri/ /index.html; + } + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; +} diff --git a/frontend/package.json b/frontend/package.json index 1817205..bb7761b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,10 +10,15 @@ "preview": "vite preview" }, "dependencies": { + "@codemirror/lang-json": "^6.0.2", + "@codemirror/state": "^6.5.4", + "@codemirror/view": "^6.39.11", + "@uiw/react-codemirror": "^4.25.4", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "react": "^19.2.0", - "react-dom": "^19.2.0" + "react-dom": "^19.2.0", + "react-router-dom": "^7.13.0" }, "devDependencies": { "@eslint/js": "^9.39.1", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 512ff04..5e6167f 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -8,6 +8,18 @@ importers: .: dependencies: + '@codemirror/lang-json': + specifier: ^6.0.2 + version: 6.0.2 + '@codemirror/state': + specifier: ^6.5.4 + version: 6.5.4 + '@codemirror/view': + specifier: ^6.39.11 + version: 6.39.11 + '@uiw/react-codemirror': + specifier: ^4.25.4 + version: 4.25.4(@babel/runtime@7.28.6)(@codemirror/autocomplete@6.20.0)(@codemirror/language@6.12.1)(@codemirror/lint@6.9.3)(@codemirror/search@6.6.0)(@codemirror/state@6.5.4)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.39.11)(codemirror@6.0.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) ajv: specifier: ^8.17.1 version: 8.17.1 @@ -20,6 +32,9 @@ importers: react-dom: specifier: ^19.2.0 version: 19.2.3(react@19.2.3) + react-router-dom: + specifier: ^7.13.0 + version: 7.13.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) devDependencies: '@eslint/js': specifier: ^9.39.1 @@ -131,6 +146,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} @@ -143,6 +162,33 @@ packages: resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} engines: {node: '>=6.9.0'} + '@codemirror/autocomplete@6.20.0': + resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==} + + '@codemirror/commands@6.10.1': + resolution: {integrity: sha512-uWDWFypNdQmz2y1LaNJzK7fL7TYKLeUAU0npEC685OKTF3KcQ2Vu3klIM78D7I6wGhktme0lh3CuQLv0ZCrD9Q==} + + '@codemirror/lang-json@6.0.2': + resolution: {integrity: sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==} + + '@codemirror/language@6.12.1': + resolution: {integrity: sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==} + + '@codemirror/lint@6.9.3': + resolution: {integrity: sha512-y3YkYhdnhjDBAe0VIA0c4wVoFOvnp8CnAvfLqi0TqotIv92wIlAAP7HELOpLBsKwjAX6W92rSflA6an/2zBvXw==} + + '@codemirror/search@6.6.0': + resolution: {integrity: sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==} + + '@codemirror/state@6.5.4': + resolution: {integrity: sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==} + + '@codemirror/theme-one-dark@6.1.3': + resolution: {integrity: sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==} + + '@codemirror/view@6.39.11': + resolution: {integrity: sha512-bWdeR8gWM87l4DB/kYSF9A+dVackzDb/V56Tq7QVrQ7rn86W0rgZFtlL3g3pem6AeGcb9NQNoy3ao4WpW4h5tQ==} + '@esbuild/aix-ppc64@0.27.2': resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} engines: {node: '>=18'} @@ -369,6 +415,21 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@lezer/common@1.5.0': + resolution: {integrity: sha512-PNGcolp9hr4PJdXR4ix7XtixDrClScvtSCYW3rQG106oVMOOI+jFb+0+J3mbeL/53g1Zd6s0kJzaw6Ri68GmAA==} + + '@lezer/highlight@1.2.3': + resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==} + + '@lezer/json@1.0.3': + resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==} + + '@lezer/lr@1.4.8': + resolution: {integrity: sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==} + + '@marijn/find-cluster-break@1.0.2': + resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + '@rolldown/pluginutils@1.0.0-beta.53': resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} @@ -585,6 +646,28 @@ packages: resolution: {integrity: sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@uiw/codemirror-extensions-basic-setup@4.25.4': + resolution: {integrity: sha512-YzNwkm0AbPv1EXhCHYR5v0nqfemG2jEB0Z3Att4rBYqKrlG7AA9Rhjc3IyBaOzsBu18wtrp9/+uhTyu7TXSRng==} + peerDependencies: + '@codemirror/autocomplete': '>=6.0.0' + '@codemirror/commands': '>=6.0.0' + '@codemirror/language': '>=6.0.0' + '@codemirror/lint': '>=6.0.0' + '@codemirror/search': '>=6.0.0' + '@codemirror/state': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + + '@uiw/react-codemirror@4.25.4': + resolution: {integrity: sha512-ipO067oyfUw+DVaXhQCxkB0ZD9b7RnY+ByrprSYSKCHaULvJ3sqWYC/Zen6zVQ8/XC4o5EPBfatGiX20kC7XGA==} + peerDependencies: + '@babel/runtime': '>=7.11.0' + '@codemirror/state': '>=6.0.0' + '@codemirror/theme-one-dark': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + codemirror: '>=6.0.0' + react: '>=17.0.0' + react-dom: '>=17.0.0' + '@vitejs/plugin-react@5.1.2': resolution: {integrity: sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -651,6 +734,9 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + codemirror@6.0.2: + resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -664,6 +750,13 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -965,6 +1058,23 @@ packages: resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} engines: {node: '>=0.10.0'} + react-router-dom@7.13.0: + resolution: {integrity: sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.13.0: + resolution: {integrity: sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + react@19.2.3: resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} engines: {node: '>=0.10.0'} @@ -994,6 +1104,9 @@ packages: engines: {node: '>=10'} hasBin: true + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1010,6 +1123,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + style-mod@4.1.3: + resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -1092,6 +1208,9 @@ packages: yaml: optional: true + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -1208,6 +1327,8 @@ snapshots: '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 + '@babel/runtime@7.28.6': {} + '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.28.6 @@ -1231,6 +1352,64 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@codemirror/autocomplete@6.20.0': + dependencies: + '@codemirror/language': 6.12.1 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.11 + '@lezer/common': 1.5.0 + + '@codemirror/commands@6.10.1': + dependencies: + '@codemirror/language': 6.12.1 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.11 + '@lezer/common': 1.5.0 + + '@codemirror/lang-json@6.0.2': + dependencies: + '@codemirror/language': 6.12.1 + '@lezer/json': 1.0.3 + + '@codemirror/language@6.12.1': + dependencies: + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.11 + '@lezer/common': 1.5.0 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + style-mod: 4.1.3 + + '@codemirror/lint@6.9.3': + dependencies: + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.11 + crelt: 1.0.6 + + '@codemirror/search@6.6.0': + dependencies: + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.11 + crelt: 1.0.6 + + '@codemirror/state@6.5.4': + dependencies: + '@marijn/find-cluster-break': 1.0.2 + + '@codemirror/theme-one-dark@6.1.3': + dependencies: + '@codemirror/language': 6.12.1 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.11 + '@lezer/highlight': 1.2.3 + + '@codemirror/view@6.39.11': + dependencies: + '@codemirror/state': 6.5.4 + crelt: 1.0.6 + style-mod: 4.1.3 + w3c-keyname: 2.2.8 + '@esbuild/aix-ppc64@0.27.2': optional: true @@ -1385,6 +1564,24 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@lezer/common@1.5.0': {} + + '@lezer/highlight@1.2.3': + dependencies: + '@lezer/common': 1.5.0 + + '@lezer/json@1.0.3': + dependencies: + '@lezer/common': 1.5.0 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@lezer/lr@1.4.8': + dependencies: + '@lezer/common': 1.5.0 + + '@marijn/find-cluster-break@1.0.2': {} + '@rolldown/pluginutils@1.0.0-beta.53': {} '@rollup/rollup-android-arm-eabi@4.55.2': @@ -1590,6 +1787,33 @@ snapshots: '@typescript-eslint/types': 8.53.1 eslint-visitor-keys: 4.2.1 + '@uiw/codemirror-extensions-basic-setup@4.25.4(@codemirror/autocomplete@6.20.0)(@codemirror/commands@6.10.1)(@codemirror/language@6.12.1)(@codemirror/lint@6.9.3)(@codemirror/search@6.6.0)(@codemirror/state@6.5.4)(@codemirror/view@6.39.11)': + dependencies: + '@codemirror/autocomplete': 6.20.0 + '@codemirror/commands': 6.10.1 + '@codemirror/language': 6.12.1 + '@codemirror/lint': 6.9.3 + '@codemirror/search': 6.6.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.11 + + '@uiw/react-codemirror@4.25.4(@babel/runtime@7.28.6)(@codemirror/autocomplete@6.20.0)(@codemirror/language@6.12.1)(@codemirror/lint@6.9.3)(@codemirror/search@6.6.0)(@codemirror/state@6.5.4)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.39.11)(codemirror@6.0.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@babel/runtime': 7.28.6 + '@codemirror/commands': 6.10.1 + '@codemirror/state': 6.5.4 + '@codemirror/theme-one-dark': 6.1.3 + '@codemirror/view': 6.39.11 + '@uiw/codemirror-extensions-basic-setup': 4.25.4(@codemirror/autocomplete@6.20.0)(@codemirror/commands@6.10.1)(@codemirror/language@6.12.1)(@codemirror/lint@6.9.3)(@codemirror/search@6.6.0)(@codemirror/state@6.5.4)(@codemirror/view@6.39.11) + codemirror: 6.0.2 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + transitivePeerDependencies: + - '@codemirror/autocomplete' + - '@codemirror/language' + - '@codemirror/lint' + - '@codemirror/search' + '@vitejs/plugin-react@5.1.2(vite@7.3.1(@types/node@24.10.9))': dependencies: '@babel/core': 7.28.6 @@ -1662,6 +1886,16 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + codemirror@6.0.2: + dependencies: + '@codemirror/autocomplete': 6.20.0 + '@codemirror/commands': 6.10.1 + '@codemirror/language': 6.12.1 + '@codemirror/lint': 6.9.3 + '@codemirror/search': 6.6.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.11 + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -1672,6 +1906,10 @@ snapshots: convert-source-map@2.0.0: {} + cookie@1.1.1: {} + + crelt@1.0.6: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -1969,6 +2207,20 @@ snapshots: react-refresh@0.18.0: {} + react-router-dom@7.13.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react-router: 7.13.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + + react-router@7.13.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + cookie: 1.1.1 + react: 19.2.3 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 19.2.3(react@19.2.3) + react@19.2.3: {} require-from-string@2.0.2: {} @@ -2012,6 +2264,8 @@ snapshots: semver@7.7.3: {} + set-cookie-parser@2.7.2: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -2022,6 +2276,8 @@ snapshots: strip-json-comments@3.1.1: {} + style-mod@4.1.3: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -2076,6 +2332,8 @@ snapshots: '@types/node': 24.10.9 fsevents: 2.3.3 + w3c-keyname@2.2.8: {} + which@2.0.2: dependencies: isexe: 2.0.0 diff --git a/frontend/public/home-logo-dark.jpeg b/frontend/public/home-logo-dark.jpeg new file mode 100644 index 0000000..fe1b934 Binary files /dev/null and b/frontend/public/home-logo-dark.jpeg differ diff --git a/frontend/public/home-logo-light.png b/frontend/public/home-logo-light.png new file mode 100644 index 0000000..c388c19 Binary files /dev/null and b/frontend/public/home-logo-light.png differ diff --git a/frontend/src/App-old.css b/frontend/src/App-old.css new file mode 100644 index 0000000..b9d355d --- /dev/null +++ b/frontend/src/App-old.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/frontend/src/App-old.tsx b/frontend/src/App-old.tsx new file mode 100644 index 0000000..168a6c8 --- /dev/null +++ b/frontend/src/App-old.tsx @@ -0,0 +1,30 @@ +import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import { ThemeProvider } from './contexts/ThemeContext'; +import { Navigation } from './components/Navigation/Navigation'; +import { Home } from './pages/Home/Home'; +import { Playground } from './pages/Playground/Playground'; +import { Engines } from './pages/Engines/Engines'; +import { Schemas } from './pages/Schemas/Schemas'; +import './styles/globals.css'; + +function App() { + return ( + + +
+ +
+ + } /> + } /> + } /> + } /> + +
+
+
+
+ ); +} + +export default App; diff --git a/frontend/src/App.css b/frontend/src/App.css index b9d355d..079bdea 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,42 +1,12 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; +.app-container { + min-height: 100vh; + display: flex; + flex-direction: column; + background-color: var(--color-bg-primary); } -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; +.main-content { + flex: 1; + display: flex; + flex-direction: column; } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 15008be..491b0b0 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,302 +1,31 @@ -import { useState, useEffect, useRef } from 'react'; -import Ajv from 'ajv/dist/2020'; -import addFormats from 'ajv-formats'; -import universalSchema from './schemas/universal.json'; -import './index.css'; - -interface Engine { - id: string; - capabilities: any; -} - -const ajv = new Ajv({ allErrors: true }); -addFormats(ajv); +import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import { ThemeProvider } from './contexts/ThemeContext'; +import { Navigation } from './components/Navigation/Navigation'; +import { Home } from './pages/Home/Home'; +import { Playground } from './pages/Playground/Playground'; +import { Engines } from './pages/Engines/Engines'; +import { Schemas } from './pages/Schemas/Schemas'; +import './styles/globals.css'; +import './App.css'; function App() { - const [engines, setEngines] = useState([]); - const [selectedEngine, setSelectedEngine] = useState(''); - /* - Default JSON is now just the instance structure. - Engine selection is handled by the UI. - */ - const [inputJson, setInputJson] = useState('{\n "metadata": { "id": "test" },\n "tasks": [],\n "candidates": [],\n "composition": {}\n}'); - const [result, setResult] = useState(null); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [theme, setTheme] = useState<'light' | 'dark'>('light'); - const fileInputRef = useRef(null); - - useEffect(() => { - fetchEngines(); - // Default to light as requested - document.documentElement.setAttribute('data-theme', 'light'); - }, []); - - const toggleTheme = () => { - const newTheme = theme === 'light' ? 'dark' : 'light'; - setTheme(newTheme); - document.documentElement.setAttribute('data-theme', newTheme); - }; - - // Validator reference to persist across renders until schema changes - const validateRef = useRef(null); - - // Initialize universal validator as fallback - useEffect(() => { - const v = ajv.compile(universalSchema); - validateRef.current = v; - }, []); - - const fetchEngines = async () => { - try { - const res = await fetch('http://localhost:8000/v1/engines'); - const data = await res.json(); - setEngines(data); - if (data.length > 0) setSelectedEngine(data[0].id); - } catch (err) { - console.error("Failed to fetch engines", err); - setEngines([]); - setSelectedEngine(''); - } - }; - - // Fetch schema when engine changes - useEffect(() => { - if (!selectedEngine) return; - - const loadSchema = async () => { - try { - console.log(`Fetching schema for ${selectedEngine}...`); - const res = await fetch(`http://localhost:8000/v1/schemas/${selectedEngine}`); - if (!res.ok) { - console.warn(`Could not fetch schema for ${selectedEngine}, using universal only.`); - validateRef.current = ajv.compile(universalSchema); - return; - } - const schema = await res.json(); - console.log(`Loaded schema for ${selectedEngine}`); - - // We need to re-compile AJV. - // Note: AJV might cache schemas by ID. If we use same ID, remove it first. - if (schema.$id && ajv.getSchema(schema.$id)) { - ajv.removeSchema(schema.$id); - } - - validateRef.current = ajv.compile(schema); - - } catch (e) { - console.error("Schema load failed", e); - validateRef.current = ajv.compile(universalSchema); - } - }; - - loadSchema(); - }, [selectedEngine]); - - const handleFileUpload = (event: React.ChangeEvent) => { - const file = event.target.files?.[0]; - if (!file) return; - - const reader = new FileReader(); - reader.onload = (e) => { - const content = e.target?.result as string; - try { - const parsed = JSON.parse(content); - // If file contains engine_id, warn or just strip it? - // User wants separation. Let's extract instance. - const instance = parsed.instance || parsed; // Handle both full envelope and raw instance - setInputJson(JSON.stringify(instance, null, 2)); - setError(null); - } catch (err) { - setError("Invalid JSON file"); - } - }; - reader.readAsText(file); - event.target.value = ''; - }; - - const handleSolve = async () => { - setLoading(true); - setError(null); - setResult(null); - - try { - let instance; - try { - instance = JSON.parse(inputJson); - } catch (e) { - throw new Error("Invalid JSON"); - } - - // Frontend Validation with Dynamic Schema - if (validateRef.current) { - const valid = validateRef.current(instance); - if (!valid) { - const errors = validateRef.current.errors?.map((e: any) => ({ - message: e.message, - path: e.instancePath, - code: "frontend_validation_error" - })) || []; - - setResult({ - errors: errors - }); - setLoading(false); - return; - } - } - - // Construct Payload - const payload = { - engine_id: selectedEngine, - instance: instance - }; - - const res = await fetch('http://localhost:8000/v1/solve', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(payload) - }); - - const data = await res.json(); - console.log("Job Response:", res.status, data); - - // Handle Job Response - if (data.status === 'failed') { - // Immediate failure (e.g. Validation Error) - setResult(data.result || { errors: [{ message: data.error, code: "job_failed" }] }); - setLoading(false); - } else if (res.status === 202) { - const jobId = data.job_id; - pollJob(jobId); - } else if (data.errors) { - // Sync validation error or failed job returned immediately (bad request pattern) - setResult(data); - setLoading(false); - } else { - // Fallback - setResult(data); - setLoading(false); - } - - } catch (err: any) { - setError(err.message); - setLoading(false); - } - }; - - const pollJob = async (jobId: string) => { - try { - const res = await fetch(`http://localhost:8000/v1/jobs/${jobId}`); - - if (!res.ok) { - throw new Error(`Job lookup failed: ${res.status} ${res.statusText}`); - } - - const data = await res.json(); - - if (data.status === 'completed') { - setResult(data.result); - setLoading(false); - } else if (data.status === 'failed') { - setError(data.error || "Job failed"); - if (data.result) setResult(data.result); // Might contain detailed errors - setLoading(false); - } else { - // Still running or queued - setTimeout(() => pollJob(jobId), 1000); // Poll every 1s - } - } catch (err: any) { - setError("Polling failed: " + err.message); - setLoading(false); - } - }; - return ( - <> -
-
- OpenBinding -
OpenBinding
-
-
-
- {loading ? 'Solving...' : 'Ready to solve!'} -
- -
-
- -
-
-
- - - - - - -
-
Input Instance (JSON)
-