From bc450e1b18af326aaa3996d6067f97993f83fbc3 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Wed, 8 Apr 2026 11:07:07 -0400 Subject: [PATCH 01/40] Add parameter estimation service migration plan Design document for migrating optimization endpoints from legacy vcell-api (/api/v0/) to Quarkus vcell-rest (/api/v1/) with database-backed job tracking, ActiveMQ messaging, and filesystem polling. Includes desktop client migration and decommissioning plan. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/parameter-estimation-service.md | 337 +++++++++++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100644 docs/parameter-estimation-service.md diff --git a/docs/parameter-estimation-service.md b/docs/parameter-estimation-service.md new file mode 100644 index 0000000000..3aa588d477 --- /dev/null +++ b/docs/parameter-estimation-service.md @@ -0,0 +1,337 @@ +# Parameter Estimation Service Migration + +## Motivation + +Parameter estimation (curve fitting) is a core VCell capability that allows users to optimize model parameters against experimental data using the COPASI optimization engine. The current implementation has been unreliable in production (GitHub #1653), and the root cause is architectural: the REST-to-batch-server communication relies on persistent in-memory TCP socket connections that are inherently fragile. + +### Problems with the current design + +1. **In-memory socket map loses state on restart.** The legacy `vcell-api` stores active optimization connections in a `Hashtable` (`paramOptActiveSockets`). When the pod restarts — which is routine in Kubernetes — all running optimization jobs become unqueryable, producing `"Can't find connection for optimization ID=..."` errors. + +2. **No persistence.** There is no database record of optimization jobs. If the API process dies, all knowledge of submitted jobs is lost. Users see a failure with no way to recover results that may have already been computed and written to disk. + +3. **Custom TCP socket protocol.** The REST layer opens a raw TCP socket to `OptimizationBatchServer:8877` on the submit service, using Java object serialization for messages (`OptMessage.*`). This is fragile (connection drops, timeouts), difficult to debug, and a security concern (deserialization of untrusted objects). + +4. **DNS-based service discovery via `nslookup`.** The REST layer shells out to `nslookup` to resolve service hostnames. The code still references Docker swarm DNS naming conventions (`tasks.vcell{SITE}_submit`) from before the migration to Kubernetes, though it works in practice because the hostname is overridden via the `vcell.submit.service.host` property. This indirection is unnecessary — Kubernetes service DNS is the standard mechanism. + +5. **Collision-prone job IDs.** Job IDs are random integers in the range 0–999,999, generated with `new Random().nextInt(1000000)`. Collisions are possible. + +6. **Legacy API.** The optimization endpoints live on the legacy Restlet-based `vcell-api` (`/api/v0/`), which is being phased out in favor of the Quarkus-based `vcell-rest` (`/api/v1/`). The legacy API has no OpenAPI spec and no auto-generated clients. + +### What works well (and should not change) + +- **The Python solver** (`pythonCopasiOpt/vcell-opt`) is correct and well-tested. It reads an `OptProblem` JSON file, runs COPASI parameter estimation, writes results to an output JSON file, and writes intermediate progress (iteration count, objective function value, best parameters) to a TSV report file. This file-based interface is clean and should be preserved exactly as-is. + +- **SLURM submission** via `SlurmProxy.submitOptimizationJob()` works. The Singularity container, bind mounts, and SLURM configuration are all correct. + +- **The shared NFS filesystem** (`/simdata`) is already mounted on both `vcell-rest` and `vcell-submit` pods, and the Python solver writes results there. This is the natural communication channel for results. + +## Design + +### Architecture overview + +``` +Desktop Client / webapp + | + | POST /api/v1/optimization (OptProblem JSON) + v +vcell-rest (Quarkus) + | 1. Validate OptProblem + | 2. Generate UUID job ID + | 3. Write OptProblem JSON to NFS: /simdata/parest_data/CopasiParest_{id}_optProblem.json + | 4. Insert vc_optjob row (status=SUBMITTED) + | 5. Send ActiveMQ message to vcell-submit + | 6. Return OptimizationJobStatus to client + v +ActiveMQ (activemqint) + | + v +vcell-submit + | 1. Receive message + | 2. Submit SLURM job via SlurmProxy (reuses existing code) + | 3. Send status update message back (QUEUED + htcJobId, or FAILED + error) + v +SLURM → Singularity container → vcell-opt Python solver (UNCHANGED) + | Writes to NFS: + | - CopasiParest_{id}_optReport.txt (progress, written incrementally) + | - CopasiParest_{id}_optRun.json (final results) + v +vcell-rest (polling on client request) + | 1. Client polls GET /api/v1/optimization/{id} + | 2. Check vc_optjob status in database + | 3. If RUNNING: read progress from report file on NFS (CopasiUtils.readProgressReportFromCSV) + | 4. If COMPLETE: read results from output file on NFS + | 5. Return OptimizationJobStatus with progress/results + v +Client displays progress or results +``` + +### Key design decisions + +**Database-backed job tracking.** Every optimization job gets a row in `vc_optjob`. The database is the source of truth for job lifecycle state (SUBMITTED → QUEUED → RUNNING → COMPLETE/FAILED/STOPPED). This survives pod restarts, supports multiple API replicas, and provides an audit trail. + +**Filesystem for data, database for state.** The OptProblem input, result output, and progress report are files on NFS — this matches the Python solver's file-based interface and avoids putting large blobs in the database. The database tracks job metadata and status; the filesystem holds the actual data. + +**ActiveMQ for job dispatch (fire-and-forget).** vcell-rest sends a message to vcell-submit to trigger SLURM submission, and vcell-submit sends a status message back. This replaces the persistent TCP socket with a reliable message broker that already exists in the deployment (`activemqint`). The messages are small (job ID + file paths), and the pattern follows the existing `ExportRequestListenerMQ` in vcell-rest. + +**UUID job IDs.** Replace the random 0–999,999 integer with UUIDs. No collision risk, no sequential enumeration. + +**Progress reporting via filesystem polling.** The Python solver already writes a TSV report file incrementally as COPASI iterates. vcell-rest reads this file directly from NFS using `CopasiUtils.readProgressReportFromCSV()` (in `vcell-core`, already a dependency). This eliminates the batch server as a middleman for progress data. Each row contains: function evaluation count, best objective value, and best parameter vector. + +**User-initiated stop via messaging.** When a user determines the optimization has sufficiently converged and stops the job, vcell-rest sends a stop message (with the SLURM job ID from the database) via ActiveMQ to vcell-submit, which calls `killJobSafe()` → `scancel`. The report file retains all progress up to the kill point, so the client can read the best parameters found. + +### Database schema + +```sql +CREATE TABLE vc_optjob ( + id varchar(64) PRIMARY KEY, + ownerRef bigint REFERENCES vc_userinfo(id), + status varchar(32) NOT NULL, + optProblemFile varchar(512) NOT NULL, + optOutputFile varchar(512) NOT NULL, + optReportFile varchar(512) NOT NULL, + htcJobId varchar(128), + statusMessage varchar(4000), + insertDate timestamp NOT NULL, + updateDate timestamp NOT NULL +); +``` + +| Column | Purpose | +|--------|---------| +| `id` | UUID job identifier | +| `ownerRef` | User who submitted (for access control) | +| `status` | SUBMITTED, QUEUED, RUNNING, COMPLETE, FAILED, STOPPED | +| `optProblemFile` | NFS path to input OptProblem JSON | +| `optOutputFile` | NFS path to result Vcellopt JSON | +| `optReportFile` | NFS path to progress report TSV | +| `htcJobId` | SLURM job ID (set when vcell-submit confirms submission) | +| `statusMessage` | Error description on failure, cancellation reason, etc. | +| `insertDate` | When the job was created | +| `updateDate` | Last status transition | + +Status transitions: +``` +SUBMITTED → QUEUED → RUNNING → COMPLETE + → FAILED + → STOPPED (user-initiated) +``` + +### REST API + +``` +POST /api/v1/optimization Submit optimization job +GET /api/v1/optimization/{id} Get job status, progress, or results +POST /api/v1/optimization/{id}/stop Stop a running job +``` + +**POST /api/v1/optimization** — Requires authenticated user. Accepts `OptProblem` JSON body. Writes input file to NFS, creates database record, sends dispatch message. Returns `OptimizationJobStatus` with the job ID and status=SUBMITTED. + +**GET /api/v1/optimization/{id}** — Requires authenticated user (must be job owner). Returns `OptimizationJobStatus` which includes: +- `status` — current job state +- `progressReport` — (when RUNNING) iteration count, objective value, best parameters from the report file +- `results` — (when COMPLETE) the full `Vcellopt` result +- `statusMessage` — (when FAILED/STOPPED) error or cancellation description + +**POST /api/v1/optimization/{id}/stop** — Requires authenticated user (must be job owner). Sends stop message to vcell-submit, updates database status to STOPPED. The client can then GET the job to read the last progress report for the best parameters found. + +### Response DTO + +```java +public record OptimizationJobStatus( + String id, + String status, + String statusMessage, + OptProgressReport progressReport, + Vcellopt results +) {} +``` + +The client checks `status` and reads the appropriate nullable field. This avoids the current string-prefix parsing pattern (`"QUEUED:"`, `"RUNNING:"`, etc.). + +## Desktop client architecture (current) + +The desktop client has a layered architecture for parameter estimation: + +### UI layer + +- **`ParameterEstimationPanel`** (`vcell-client/.../biomodel/ParameterEstimationPanel.java`) — Container panel with tabs for data, parameters, and run configuration. +- **`ParameterEstimationRunTaskPanel`** (`vcell-client/.../optimization/gui/ParameterEstimationRunTaskPanel.java`) — Main run panel. The `solve()` method (line 1126) dispatches an async task chain that calls `CopasiOptimizationSolverRemote.solveRemoteApi()`. +- **`RunStatusProgressDialog`** (inner class of `ParameterEstimationRunTaskPanel`, line 101) — Modal dialog showing real-time progress: number of evaluations, objective function value, and a log10(error) vs evaluations plot. + +### Solver coordination layer + +- **`CopasiOptimizationSolverRemote`** (`vcell-client/.../copasi/CopasiOptimizationSolverRemote.java`) — Orchestrates the remote optimization call. The `solveRemoteApi()` method (line 27): + 1. Converts `ParameterEstimationTask` → `OptProblem` via `CopasiUtils.paramTaskToOptProblem()` + 2. Calls `apiClient.submitOptimization(optProblemJson)` → gets back optimization ID + 3. Polls `apiClient.getOptRunJson(optId, bStopRequested)` every ~2 seconds + 4. Parses string-prefixed responses (`"QUEUED:"`, `"RUNNING:"`, etc.) and raw JSON for `OptProgressReport` and `Vcellopt` + 5. Updates `CopasiOptSolverCallbacks` with progress, which fires property change events to the UI + +### Callback / event layer + +- **`CopasiOptSolverCallbacks`** (`vcell-core/.../optimization/CopasiOptSolverCallbacks.java`) — Bridges the solver and the UI via `PropertyChangeListener`. Carries `OptProgressReport` (progress data) and `stopRequested` (user stop signal). + +### API client layer + +- **`VCellApiClient`** (`vcell-apiclient/.../api/client/VCellApiClient.java`) — HTTP client for the legacy `/api/v0/` endpoints: + - `submitOptimization()` (line 235) — POST to `/api/v0/optimization`, returns optimization ID as plain text + - `getOptRunJson()` (line 219) — GET to `/api/v0/optimization/{id}?bStop={bStop}`, returns raw JSON string that the caller must parse by inspecting string prefixes + +- **`ClientServerManager`** (`vcell-apiclient/.../server/ClientServerManager.java`) — Provides the `VCellApiClient` instance to the desktop client. + +### What changes in the migration + +The new auto-generated `OptimizationResourceApi` (from `vcell-restclient`) replaces `VCellApiClient` for optimization. The key improvements: + +1. **Typed responses.** `getOptimizationStatus()` returns `OptimizationJobStatus` with explicit `status`, `progressReport`, and `results` fields — replacing the error-prone string-prefix parsing in `CopasiOptimizationSolverRemote`. +2. **Separate stop endpoint.** `POST /{id}/stop` replaces the `bStop` query parameter hack on the GET endpoint. +3. **Auto-generated.** The Java client is generated from the OpenAPI spec, so it stays in sync with the server automatically. + +The UI layer (`ParameterEstimationRunTaskPanel`, `RunStatusProgressDialog`) and the callback layer (`CopasiOptSolverCallbacks`) are **unchanged** — they already consume `OptProgressReport` and `Vcellopt` objects, which are the same types the new API returns. + +## Implementation plan + +### Commit 1: Database schema and service layer + +- Add `vc_optjob` table to `vcell-rest/src/main/resources/scripts/init.sql` +- Create `OptimizationJobStatus` DTO in `vcell-rest` +- Create `OptimizationRestService` (`@ApplicationScoped`) with database CRUD operations and filesystem read methods +- Oracle migration DDL script for production + +### Commit 2: REST endpoints + +- Create `OptimizationResource.java` in `vcell-rest/src/main/java/org/vcell/restq/handlers/` +- Endpoints: submit, status, stop +- Authentication and ownership checks +- Follow patterns from `SimulationResource.java` + +### Commit 3: ActiveMQ messaging (vcell-rest side) + +- Add AMQP channel configuration to `application.properties` +- Create `OptimizationMQProducer` for sending submit/stop commands +- Create `OptimizationMQConsumer` for receiving status updates from vcell-submit +- Update `vc_optjob` status on incoming messages + +### Commit 4: ActiveMQ messaging (vcell-submit side) + +- Add JMS queue listener in `HtcSimulationWorker` for optimization requests +- On "submit": call `SlurmProxy.submitOptimizationJob()` (existing code), send back QUEUED + htcJobId +- On "stop": call `killJobSafe()` with SLURM job ID from message +- Reuse existing `OptimizationBatchServer.submitOptProblem()` logic + +### Commit 5: Tests (three levels) + +Three levels of integration testing, each building on the previous: + +#### Level 1: REST + DB + filesystem — `@Tag("Quarkus")` + +Runs in CI. Uses testcontainers for PostgreSQL and Keycloak (already configured for other Quarkus tests). The ActiveMQ producer is mocked. Simulates solver completion by writing fake report/output files to a temp directory. + +Create `OptimizationApiTest.java` (`@QuarkusTest`): +- Test submit: POST OptProblem, verify DB record created, verify OptProblem file written to disk +- Test status polling: write mock report file with progress rows, call GET, verify `OptimizationJobStatus` contains correct `OptProgressReport` (iteration count, objective value, best parameters) +- Test completion: write mock output file, call GET, verify status=COMPLETE with `Vcellopt` results +- Test stop: POST stop, verify DB status updated to STOPPED +- Test authorization: verify user can only access their own jobs +- Test error: verify FAILED status with `statusMessage` error description + +#### Level 2: REST + DB + ActiveMQ round-trip — `@Tag("Quarkus")` + +Runs in CI. Adds an ActiveMQ testcontainer. Both the vcell-rest producer and a test-harness consumer run in the same JVM. The test consumer simulates vcell-submit: it receives the submit message, writes mock result files to the temp directory, and sends a status update message back. + +Create `OptimizationMQTest.java` (`@QuarkusTest`): +- Test submit → message received by consumer → status update sent back → DB updated to QUEUED with htcJobId +- Test stop → stop message received by consumer → DB updated to STOPPED +- Test failure → consumer sends FAILED status with error description → DB and GET endpoint reflect failure + +This tests the full messaging contract between vcell-rest and vcell-submit without needing SLURM. + +#### Level 3: Full end-to-end with SLURM — `@Tag("SLURM_IT")` + +Does NOT run in CI. Requires a developer on the network with NFS mounted and SSH access to the SLURM cluster. Configured via system properties: + +``` +vcell.test.slurm.host SLURM login node (e.g. login.hpc.cam.uchc.edu) +vcell.test.slurm.user SSH user for SLURM submission (e.g. vcell) +vcell.test.slurm.keypath Path to SSH private key (e.g. /path/to/id_rsa) +vcell.test.slurm.singularity.image Path to vcell-opt Singularity image on cluster +vcell.test.nfs.parestdir NFS path for optimization data, accessible from + both test machine and SLURM (e.g. /simdata/parest_data) +``` + +The test is skipped (via JUnit `Assumptions.assumeTrue`) if any required property is missing. + +Create `OptimizationSlurmIT.java` (`@QuarkusTest`): +- Uses testcontainers for PostgreSQL and ActiveMQ +- Includes a real submit-side handler (in-process) that SSHes to SLURM and submits the job using `SlurmProxy` +- Submits a small, fast-converging OptProblem (few parameters, few data points, low max iterations) to minimize SLURM runtime +- Polls the status endpoint with a generous timeout (5 minutes, to account for SLURM queue wait) +- Verifies that: + - Progress reports arrive with real iteration counts and objective function values + - The final result contains optimized parameter values + - The parameter values are reasonable (within expected bounds) +- Tests user-initiated stop: submit a longer-running problem, poll until RUNNING with progress, stop, verify report file has partial progress + +This test exercises the complete production path: REST → DB → ActiveMQ → SSH → SLURM → Singularity → Python/COPASI → NFS → filesystem polling → REST response. + +### Commit 6: Regenerate OpenAPI clients + +- Run `tools/generate.sh` +- Verify downstream: `mvn compile test-compile -pl vcell-rest -am` +- New `OptimizationResourceApi` class generated for Java, Python, TypeScript clients + +### Commit 7: Update desktop client + +- Modify `CopasiOptimizationSolverRemote.solveRemoteApi()` to use the auto-generated `OptimizationResourceApi` instead of `VCellApiClient` +- Replace string-prefix parsing with typed `OptimizationJobStatus` fields: + - `status` field replaces parsing for `"QUEUED:"`, `"RUNNING:"`, `"FAILED:"` prefixes + - `progressReport` field replaces JSON deserialization of embedded progress strings + - `results` field replaces JSON deserialization of the final `Vcellopt` +- Use `POST /{id}/stop` for stop requests instead of the `bStop` query parameter on GET +- The UI layer (`ParameterEstimationRunTaskPanel`, `RunStatusProgressDialog`) and the callback layer (`CopasiOptSolverCallbacks`) require no changes — they already consume `OptProgressReport` and `Vcellopt` objects + +### Commit 8: Remove legacy optimization code + +- Delete `OptimizationRunServerResource.java` from `vcell-api` +- Delete `OptimizationRunResource.java` (interface) from `vcell-api` +- Delete `OptMessage.java` from `vcell-core` +- Remove socket server from `OptimizationBatchServer` (`initOptimizationSocket()`, `OptCommunicationThread`) +- Remove socket initialization from `HtcSimulationWorker.init()` +- Remove `submitOptimization()` / `getOptRunJson()` from `VCellApiClient` +- Remove optimization route registration from `VCellApiApplication.java` +- Refactor remaining `OptimizationBatchServer` methods into a focused `OptimizationJobSubmitter` class + +### Commit 9: Kubernetes configuration + +- Remove port 8877 from vcell-submit Service +- Verify NFS mount paths for vcell-rest pod include `parest_data` directory +- Update ingress if needed for new `/api/v1/optimization` route + +## Decommissioning the legacy `/api/v0/optimization` endpoints + +The legacy endpoints must remain available during a transition period because deployed desktop clients (already installed on user machines) will continue to call `/api/v0/optimization` until they update. The decommissioning plan: + +### Phase 1: Parallel operation (commits 1–6) + +Both old and new endpoints are live. The new `/api/v1/optimization` endpoints are deployed and functional. The old `/api/v0/optimization` endpoints continue to work as before (same socket-based implementation, same bugs). No client changes yet. + +### Phase 2: Client migration (commit 7) + +The desktop client is updated to call `/api/v1/optimization`. New client builds use the new API exclusively. Old client installs still use `/api/v0/`. + +VCell uses a managed client update mechanism — when users launch the desktop client, it checks for updates and prompts to download the latest version. This means the transition window depends on how quickly users update. + +### Phase 3: Deprecation monitoring + +After the updated client is released: +- Add logging to the legacy `/api/v0/optimization` endpoints to track usage +- Monitor for a period (e.g., 2–4 weeks) to see if any clients are still hitting the old endpoints +- Communicate the deprecation via the VCell user mailing list if needed + +### Phase 4: Removal (commit 8) + +Once legacy endpoint usage drops to zero (or an acceptable threshold): +- Remove the legacy optimization endpoints, socket server, and related code +- Remove port 8877 from the vcell-submit Kubernetes service (commit 9) +- The `VCellApiClient` class itself is not deleted (it may still be used for other legacy endpoints), but its optimization methods are removed From 1931a6d9c4183cd23106f786288b7851243d64e0 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Wed, 8 Apr 2026 11:09:21 -0400 Subject: [PATCH 02/40] Add optimization job database schema, DTOs, and service layer - Add vc_optjob table to init.sql for database-backed job tracking - Add OptJobStatus enum (SUBMITTED, QUEUED, RUNNING, COMPLETE, FAILED, STOPPED) - Add OptimizationJobStatus response DTO with progress and results fields - Add OptimizationRestService with submit, status polling, and update methods - Writes OptProblem to NFS filesystem - Reads progress/results from filesystem via CopasiUtils - JDBC operations for vc_optjob table Co-Authored-By: Claude Opus 4.6 (1M context) --- .../org/vcell/restq/models/OptJobStatus.java | 10 + .../restq/models/OptimizationJobStatus.java | 13 + .../services/OptimizationRestService.java | 275 ++++++++++++++++++ .../src/main/resources/scripts/init.sql | 1 + 4 files changed, 299 insertions(+) create mode 100644 vcell-rest/src/main/java/org/vcell/restq/models/OptJobStatus.java create mode 100644 vcell-rest/src/main/java/org/vcell/restq/models/OptimizationJobStatus.java create mode 100644 vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java diff --git a/vcell-rest/src/main/java/org/vcell/restq/models/OptJobStatus.java b/vcell-rest/src/main/java/org/vcell/restq/models/OptJobStatus.java new file mode 100644 index 0000000000..3d2dede18d --- /dev/null +++ b/vcell-rest/src/main/java/org/vcell/restq/models/OptJobStatus.java @@ -0,0 +1,10 @@ +package org.vcell.restq.models; + +public enum OptJobStatus { + SUBMITTED, + QUEUED, + RUNNING, + COMPLETE, + FAILED, + STOPPED +} diff --git a/vcell-rest/src/main/java/org/vcell/restq/models/OptimizationJobStatus.java b/vcell-rest/src/main/java/org/vcell/restq/models/OptimizationJobStatus.java new file mode 100644 index 0000000000..74ad69ba3b --- /dev/null +++ b/vcell-rest/src/main/java/org/vcell/restq/models/OptimizationJobStatus.java @@ -0,0 +1,13 @@ +package org.vcell.restq.models; + +import org.vcell.optimization.jtd.OptProgressReport; +import org.vcell.optimization.jtd.Vcellopt; + +public record OptimizationJobStatus( + String id, + OptJobStatus status, + String statusMessage, + String htcJobId, + OptProgressReport progressReport, + Vcellopt results +) {} diff --git a/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java b/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java new file mode 100644 index 0000000000..c81613e036 --- /dev/null +++ b/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java @@ -0,0 +1,275 @@ +package org.vcell.restq.services; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.enterprise.context.ApplicationScoped; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.vcell.optimization.CopasiUtils; +import org.vcell.optimization.jtd.OptProblem; +import org.vcell.optimization.jtd.OptProgressReport; +import org.vcell.optimization.jtd.Vcellopt; +import org.vcell.restq.db.AgroalConnectionFactory; +import org.vcell.restq.models.OptJobStatus; +import org.vcell.restq.models.OptimizationJobStatus; +import org.vcell.util.DataAccessException; +import org.vcell.util.document.User; + +import java.io.File; +import java.io.IOException; +import java.sql.*; +import java.time.Instant; +import java.util.UUID; + +@ApplicationScoped +public class OptimizationRestService { + private static final Logger lg = LogManager.getLogger(OptimizationRestService.class); + + private final AgroalConnectionFactory connectionFactory; + private final ObjectMapper objectMapper; + + public OptimizationRestService(AgroalConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + this.objectMapper = new ObjectMapper(); + } + + /** + * Submit a new optimization job. Writes the OptProblem to the filesystem and creates a database record. + * + * @param optProblem the optimization problem to solve + * @param parestDataDir the directory for optimization data files (e.g. /simdata/parest_data) + * @param user the authenticated user submitting the job + * @return the initial job status with the assigned job ID + */ + public OptimizationJobStatus submitOptimization(OptProblem optProblem, File parestDataDir, User user) + throws SQLException, IOException { + String jobId = UUID.randomUUID().toString(); + String filePrefix = "CopasiParest_" + jobId; + + File optProblemFile = new File(parestDataDir, filePrefix + "_optProblem.json"); + File optOutputFile = new File(parestDataDir, filePrefix + "_optRun.json"); + File optReportFile = new File(parestDataDir, filePrefix + "_optReport.txt"); + + // Write the OptProblem to the filesystem + if (!parestDataDir.exists()) { + parestDataDir.mkdirs(); + } + CopasiUtils.writeOptProblem(optProblemFile, optProblem); + + // Insert the database record + Instant now = Instant.now(); + insertOptJob(jobId, user, OptJobStatus.SUBMITTED, + optProblemFile.getAbsolutePath(), optOutputFile.getAbsolutePath(), optReportFile.getAbsolutePath(), + null, null, now); + + return new OptimizationJobStatus(jobId, OptJobStatus.SUBMITTED, null, null, null, null); + } + + /** + * Get the current status of an optimization job, including progress or results if available. + */ + public OptimizationJobStatus getOptimizationStatus(String jobId, User user) + throws SQLException, DataAccessException, IOException { + OptJobRecord record = getOptJobRecord(jobId); + if (record == null) { + throw new DataAccessException("Optimization job not found: " + jobId); + } + if (record.ownerRef != user.getID().toString().hashCode() && !record.ownerUserid.equals(user.getName())) { + // check actual ownership via user key + if (!Long.toString(record.ownerRef).equals(user.getID().toString())) { + throw new DataAccessException("Not authorized to access optimization job: " + jobId); + } + } + + OptProgressReport progressReport = null; + Vcellopt results = null; + + switch (record.status) { + case RUNNING: + case QUEUED: + // Try to read progress from the report file + progressReport = readProgressReport(record.optReportFile); + // Check if the output file has appeared (solver completed) + results = readResults(record.optOutputFile); + if (results != null) { + updateOptJobStatus(jobId, OptJobStatus.COMPLETE, null); + return new OptimizationJobStatus(jobId, OptJobStatus.COMPLETE, null, record.htcJobId, progressReport, results); + } + break; + case COMPLETE: + progressReport = readProgressReport(record.optReportFile); + results = readResults(record.optOutputFile); + break; + case STOPPED: + // After stop, the report file has progress up to the kill point + progressReport = readProgressReport(record.optReportFile); + break; + case FAILED: + case SUBMITTED: + // No progress or results expected + break; + } + + return new OptimizationJobStatus(jobId, record.status, record.statusMessage, record.htcJobId, progressReport, results); + } + + /** + * Update the status of an optimization job (e.g. when vcell-submit reports back). + */ + public void updateOptJobStatus(String jobId, OptJobStatus status, String statusMessage) + throws SQLException { + Connection con = null; + try { + con = connectionFactory.getConnection(this); + PreparedStatement stmt = con.prepareStatement( + "UPDATE vc_optjob SET status = ?, statusMessage = ?, updateDate = ? WHERE id = ?"); + stmt.setString(1, status.name()); + stmt.setString(2, statusMessage); + stmt.setTimestamp(3, Timestamp.from(Instant.now())); + stmt.setString(4, jobId); + stmt.executeUpdate(); + con.commit(); + } catch (SQLException e) { + if (con != null) con.rollback(); + throw e; + } finally { + if (con != null) connectionFactory.release(con, this); + } + } + + /** + * Update the SLURM job ID after vcell-submit confirms submission. + */ + public void updateHtcJobId(String jobId, String htcJobId) throws SQLException { + Connection con = null; + try { + con = connectionFactory.getConnection(this); + PreparedStatement stmt = con.prepareStatement( + "UPDATE vc_optjob SET htcJobId = ?, status = ?, updateDate = ? WHERE id = ?"); + stmt.setString(1, htcJobId); + stmt.setString(2, OptJobStatus.QUEUED.name()); + stmt.setTimestamp(3, Timestamp.from(Instant.now())); + stmt.setString(4, jobId); + stmt.executeUpdate(); + con.commit(); + } catch (SQLException e) { + if (con != null) con.rollback(); + throw e; + } finally { + if (con != null) connectionFactory.release(con, this); + } + } + + /** + * Get the SLURM job ID for a given optimization job (needed for stop). + */ + public String getHtcJobId(String jobId) throws SQLException, DataAccessException { + OptJobRecord record = getOptJobRecord(jobId); + if (record == null) { + throw new DataAccessException("Optimization job not found: " + jobId); + } + return record.htcJobId; + } + + // --- Private helpers --- + + private void insertOptJob(String jobId, User user, OptJobStatus status, + String optProblemFile, String optOutputFile, String optReportFile, + String htcJobId, String statusMessage, Instant now) throws SQLException { + Connection con = null; + try { + con = connectionFactory.getConnection(this); + PreparedStatement stmt = con.prepareStatement( + "INSERT INTO vc_optjob (id, ownerRef, status, optProblemFile, optOutputFile, optReportFile, " + + "htcJobId, statusMessage, insertDate, updateDate) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + stmt.setString(1, jobId); + stmt.setLong(2, Long.parseLong(user.getID().toString())); + stmt.setString(3, status.name()); + stmt.setString(4, optProblemFile); + stmt.setString(5, optOutputFile); + stmt.setString(6, optReportFile); + stmt.setString(7, htcJobId); + stmt.setString(8, statusMessage); + stmt.setTimestamp(9, Timestamp.from(now)); + stmt.setTimestamp(10, Timestamp.from(now)); + stmt.executeUpdate(); + con.commit(); + } catch (SQLException e) { + if (con != null) con.rollback(); + throw e; + } finally { + if (con != null) connectionFactory.release(con, this); + } + } + + private OptJobRecord getOptJobRecord(String jobId) throws SQLException { + Connection con = null; + try { + con = connectionFactory.getConnection(this); + PreparedStatement stmt = con.prepareStatement( + "SELECT j.id, j.ownerRef, j.status, j.optProblemFile, j.optOutputFile, j.optReportFile, " + + "j.htcJobId, j.statusMessage, j.insertDate, j.updateDate, u.userid " + + "FROM vc_optjob j JOIN vc_userinfo u ON j.ownerRef = u.id WHERE j.id = ?"); + stmt.setString(1, jobId); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + return new OptJobRecord( + rs.getString("id"), + rs.getLong("ownerRef"), + rs.getString("userid"), + OptJobStatus.valueOf(rs.getString("status")), + rs.getString("optProblemFile"), + rs.getString("optOutputFile"), + rs.getString("optReportFile"), + rs.getString("htcJobId"), + rs.getString("statusMessage"), + rs.getTimestamp("insertDate").toInstant(), + rs.getTimestamp("updateDate").toInstant() + ); + } + return null; + } finally { + if (con != null) connectionFactory.release(con, this); + } + } + + private OptProgressReport readProgressReport(String reportFilePath) { + try { + File f = new File(reportFilePath); + if (f.exists()) { + return CopasiUtils.readProgressReportFromCSV(f); + } + } catch (IOException e) { + lg.warn("Failed to read progress report from {}: {}", reportFilePath, e.getMessage()); + } + return null; + } + + private Vcellopt readResults(String outputFilePath) { + try { + File f = new File(outputFilePath); + if (f.exists()) { + return objectMapper.readValue(f, Vcellopt.class); + } + } catch (IOException e) { + lg.warn("Failed to read optimization results from {}: {}", outputFilePath, e.getMessage()); + } + return null; + } + + /** + * Internal record for database row data. + */ + record OptJobRecord( + String id, + long ownerRef, + String ownerUserid, + OptJobStatus status, + String optProblemFile, + String optOutputFile, + String optReportFile, + String htcJobId, + String statusMessage, + Instant insertDate, + Instant updateDate + ) {} +} diff --git a/vcell-rest/src/main/resources/scripts/init.sql b/vcell-rest/src/main/resources/scripts/init.sql index f338f9d49a..a7ff0062d7 100644 --- a/vcell-rest/src/main/resources/scripts/init.sql +++ b/vcell-rest/src/main/resources/scripts/init.sql @@ -76,6 +76,7 @@ CREATE TABLE vc_userlogininfo(id bigint PRIMARY KEY,userRef bigint NOT NULL REFE CREATE TABLE vc_metadata(id bigint PRIMARY KEY,bioModelRef bigint NOT NULL REFERENCES vc_biomodel(id) ON DELETE CASCADE,vcMetaDataLarge text ,vcMetaDataSmall varchar(4000) ); CREATE TABLE vc_simdelfromdisk(deldate varchar(20) ,userid varchar(255) NOT NULL,userkey bigint ,simid bigint ,simpref bigint ,simdate varchar(20) ,simname varchar(255) NOT NULL,status varchar(10) ,numfiles bigint ,totalsize bigint ); CREATE TABLE vc_useridentity(id bigint PRIMARY KEY,userRef bigint NOT NULL REFERENCES vc_userinfo(id),authSubject varchar(128) NOT NULL,authIssuer varchar(128) NOT NULL,insertDate timestamp NOT NULL); +CREATE TABLE vc_optjob(id varchar(64) PRIMARY KEY,ownerRef bigint NOT NULL REFERENCES vc_userinfo(id),status varchar(32) NOT NULL,optProblemFile varchar(512) NOT NULL,optOutputFile varchar(512) NOT NULL,optReportFile varchar(512) NOT NULL,htcJobId varchar(128),statusMessage varchar(4000),insertDate timestamp NOT NULL,updateDate timestamp NOT NULL); CREATE VIEW public.dual AS SELECT CAST('X' as varchar) AS dummy; From 3c9903259305d80fcb8aafaa19c1e85aad776ae4 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Wed, 8 Apr 2026 11:09:59 -0400 Subject: [PATCH 03/40] Add REST endpoints for parameter estimation optimization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - POST /api/v1/optimization — submit optimization job - GET /api/v1/optimization/{optId} — get status, progress, or results - POST /api/v1/optimization/{optId}/stop — stop a running job All endpoints require authenticated user. Status endpoint returns OptimizationJobStatus with typed fields for progress and results. ActiveMQ dispatch to vcell-submit is marked TODO for commit 3. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../restq/handlers/OptimizationResource.java | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java diff --git a/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java b/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java new file mode 100644 index 0000000000..fc5fd21ef4 --- /dev/null +++ b/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java @@ -0,0 +1,106 @@ +package org.vcell.restq.handlers; + +import io.quarkus.security.identity.SecurityIdentity; +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.vcell.optimization.jtd.OptProblem; +import org.vcell.restq.errors.exceptions.DataAccessWebException; +import org.vcell.restq.errors.exceptions.NotAuthenticatedWebException; +import org.vcell.restq.errors.exceptions.NotFoundWebException; +import org.vcell.restq.errors.exceptions.RuntimeWebException; +import org.vcell.restq.models.OptJobStatus; +import org.vcell.restq.models.OptimizationJobStatus; +import org.vcell.restq.services.OptimizationRestService; +import org.vcell.restq.services.UserRestService; +import org.vcell.util.DataAccessException; +import org.vcell.util.document.User; + +import java.io.File; +import java.io.IOException; +import java.sql.SQLException; + +@Path("/api/v1/optimization") +@Produces(MediaType.APPLICATION_JSON) +public class OptimizationResource { + + private final OptimizationRestService optimizationRestService; + + @Inject + SecurityIdentity securityIdentity; + + @Inject + UserRestService userRestService; + + // TODO: make configurable via application.properties + private static final String PAREST_DATA_DIR = "/simdata/parest_data"; + + @Inject + public OptimizationResource(OptimizationRestService optimizationRestService) { + this.optimizationRestService = optimizationRestService; + } + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @RolesAllowed("user") + @Operation(operationId = "submitOptimization", summary = "Submit a new parameter estimation optimization job") + public OptimizationJobStatus submit(OptProblem optProblem) + throws NotAuthenticatedWebException, DataAccessWebException { + try { + User vcellUser = userRestService.getUserFromIdentity(securityIdentity); + OptimizationJobStatus status = optimizationRestService.submitOptimization( + optProblem, new File(PAREST_DATA_DIR), vcellUser); + // TODO: send ActiveMQ message to vcell-submit for SLURM dispatch (commit 3) + return status; + } catch (SQLException e) { + throw new RuntimeWebException("Failed to submit optimization job: " + e.getMessage(), e); + } catch (IOException e) { + throw new RuntimeWebException("Failed to write optimization problem to filesystem: " + e.getMessage(), e); + } + } + + @GET + @Path("{optId}") + @RolesAllowed("user") + @Operation(operationId = "getOptimizationStatus", summary = "Get status, progress, or results of an optimization job") + public OptimizationJobStatus getStatus(@PathParam("optId") String optId) + throws NotAuthenticatedWebException, NotFoundWebException, DataAccessWebException { + try { + User vcellUser = userRestService.getUserFromIdentity(securityIdentity); + return optimizationRestService.getOptimizationStatus(optId, vcellUser); + } catch (DataAccessException e) { + throw new DataAccessWebException(e.getMessage(), e); + } catch (SQLException e) { + throw new RuntimeWebException("Failed to query optimization status: " + e.getMessage(), e); + } catch (IOException e) { + throw new RuntimeWebException("Failed to read optimization data: " + e.getMessage(), e); + } + } + + @POST + @Path("{optId}/stop") + @RolesAllowed("user") + @Operation(operationId = "stopOptimization", summary = "Stop a running optimization job") + public OptimizationJobStatus stop(@PathParam("optId") String optId) + throws NotAuthenticatedWebException, NotFoundWebException, DataAccessWebException { + try { + User vcellUser = userRestService.getUserFromIdentity(securityIdentity); + // Verify ownership by fetching status (throws if not authorized) + OptimizationJobStatus currentStatus = optimizationRestService.getOptimizationStatus(optId, vcellUser); + if (currentStatus.status() != OptJobStatus.RUNNING && currentStatus.status() != OptJobStatus.QUEUED) { + throw new DataAccessWebException("Cannot stop optimization job in state: " + currentStatus.status()); + } + // TODO: send ActiveMQ stop message to vcell-submit with htcJobId (commit 3) + optimizationRestService.updateOptJobStatus(optId, OptJobStatus.STOPPED, "Stopped by user"); + return optimizationRestService.getOptimizationStatus(optId, vcellUser); + } catch (DataAccessException e) { + throw new DataAccessWebException(e.getMessage(), e); + } catch (SQLException e) { + throw new RuntimeWebException("Failed to stop optimization job: " + e.getMessage(), e); + } catch (IOException e) { + throw new RuntimeWebException("Failed to read optimization data: " + e.getMessage(), e); + } + } +} From 51973af93ff8bef23a755036431e49834cd1d62a Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Wed, 8 Apr 2026 11:42:48 -0400 Subject: [PATCH 04/40] Refactor optimization job ID from UUID to bigint database sequence key Use VCell convention: bigint primary key from newSeq database sequence, consistent with all other VCell tables. Uses KeyValue type and KeyFactory.getNewKey() infrastructure throughout. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/parameter-estimation-service.md | 10 +- .../restq/handlers/OptimizationResource.java | 14 +- .../restq/models/OptimizationJobStatus.java | 3 +- .../services/OptimizationRestService.java | 138 +++++++++--------- .../src/main/resources/scripts/init.sql | 2 +- 5 files changed, 84 insertions(+), 83 deletions(-) diff --git a/docs/parameter-estimation-service.md b/docs/parameter-estimation-service.md index 3aa588d477..08a607301a 100644 --- a/docs/parameter-estimation-service.md +++ b/docs/parameter-estimation-service.md @@ -74,7 +74,7 @@ Client displays progress or results **ActiveMQ for job dispatch (fire-and-forget).** vcell-rest sends a message to vcell-submit to trigger SLURM submission, and vcell-submit sends a status message back. This replaces the persistent TCP socket with a reliable message broker that already exists in the deployment (`activemqint`). The messages are small (job ID + file paths), and the pattern follows the existing `ExportRequestListenerMQ` in vcell-rest. -**UUID job IDs.** Replace the random 0–999,999 integer with UUIDs. No collision risk, no sequential enumeration. +**Database-sequence job IDs.** Replace the random 0–999,999 integer with `bigint` keys from the shared `newSeq` database sequence, consistent with every other VCell table (`vc_biomodel`, `vc_simulation`, etc.). Uses the existing `KeyValue` type and `KeyFactory.getNewKey()` infrastructure. **Progress reporting via filesystem polling.** The Python solver already writes a TSV report file incrementally as COPASI iterates. vcell-rest reads this file directly from NFS using `CopasiUtils.readProgressReportFromCSV()` (in `vcell-core`, already a dependency). This eliminates the batch server as a middleman for progress data. Each row contains: function evaluation count, best objective value, and best parameter vector. @@ -84,7 +84,7 @@ Client displays progress or results ```sql CREATE TABLE vc_optjob ( - id varchar(64) PRIMARY KEY, + id bigint PRIMARY KEY, ownerRef bigint REFERENCES vc_userinfo(id), status varchar(32) NOT NULL, optProblemFile varchar(512) NOT NULL, @@ -99,7 +99,7 @@ CREATE TABLE vc_optjob ( | Column | Purpose | |--------|---------| -| `id` | UUID job identifier | +| `id` | Database sequence key (bigint, from `newSeq`) | | `ownerRef` | User who submitted (for access control) | | `status` | SUBMITTED, QUEUED, RUNNING, COMPLETE, FAILED, STOPPED | | `optProblemFile` | NFS path to input OptProblem JSON | @@ -139,8 +139,8 @@ POST /api/v1/optimization/{id}/stop Stop a running job ```java public record OptimizationJobStatus( - String id, - String status, + KeyValue id, + OptJobStatus status, String statusMessage, OptProgressReport progressReport, Vcellopt results diff --git a/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java b/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java index fc5fd21ef4..44c04647f1 100644 --- a/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java +++ b/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java @@ -16,6 +16,7 @@ import org.vcell.restq.services.OptimizationRestService; import org.vcell.restq.services.UserRestService; import org.vcell.util.DataAccessException; +import org.vcell.util.document.KeyValue; import org.vcell.util.document.User; import java.io.File; @@ -65,11 +66,11 @@ public OptimizationJobStatus submit(OptProblem optProblem) @Path("{optId}") @RolesAllowed("user") @Operation(operationId = "getOptimizationStatus", summary = "Get status, progress, or results of an optimization job") - public OptimizationJobStatus getStatus(@PathParam("optId") String optId) + public OptimizationJobStatus getStatus(@PathParam("optId") Long optId) throws NotAuthenticatedWebException, NotFoundWebException, DataAccessWebException { try { User vcellUser = userRestService.getUserFromIdentity(securityIdentity); - return optimizationRestService.getOptimizationStatus(optId, vcellUser); + return optimizationRestService.getOptimizationStatus(new KeyValue(optId.toString()), vcellUser); } catch (DataAccessException e) { throw new DataAccessWebException(e.getMessage(), e); } catch (SQLException e) { @@ -83,18 +84,19 @@ public OptimizationJobStatus getStatus(@PathParam("optId") String optId) @Path("{optId}/stop") @RolesAllowed("user") @Operation(operationId = "stopOptimization", summary = "Stop a running optimization job") - public OptimizationJobStatus stop(@PathParam("optId") String optId) + public OptimizationJobStatus stop(@PathParam("optId") Long optId) throws NotAuthenticatedWebException, NotFoundWebException, DataAccessWebException { try { User vcellUser = userRestService.getUserFromIdentity(securityIdentity); + KeyValue jobKey = new KeyValue(optId.toString()); // Verify ownership by fetching status (throws if not authorized) - OptimizationJobStatus currentStatus = optimizationRestService.getOptimizationStatus(optId, vcellUser); + OptimizationJobStatus currentStatus = optimizationRestService.getOptimizationStatus(jobKey, vcellUser); if (currentStatus.status() != OptJobStatus.RUNNING && currentStatus.status() != OptJobStatus.QUEUED) { throw new DataAccessWebException("Cannot stop optimization job in state: " + currentStatus.status()); } // TODO: send ActiveMQ stop message to vcell-submit with htcJobId (commit 3) - optimizationRestService.updateOptJobStatus(optId, OptJobStatus.STOPPED, "Stopped by user"); - return optimizationRestService.getOptimizationStatus(optId, vcellUser); + optimizationRestService.updateOptJobStatus(jobKey, OptJobStatus.STOPPED, "Stopped by user"); + return optimizationRestService.getOptimizationStatus(jobKey, vcellUser); } catch (DataAccessException e) { throw new DataAccessWebException(e.getMessage(), e); } catch (SQLException e) { diff --git a/vcell-rest/src/main/java/org/vcell/restq/models/OptimizationJobStatus.java b/vcell-rest/src/main/java/org/vcell/restq/models/OptimizationJobStatus.java index 74ad69ba3b..8bc969df4c 100644 --- a/vcell-rest/src/main/java/org/vcell/restq/models/OptimizationJobStatus.java +++ b/vcell-rest/src/main/java/org/vcell/restq/models/OptimizationJobStatus.java @@ -2,9 +2,10 @@ import org.vcell.optimization.jtd.OptProgressReport; import org.vcell.optimization.jtd.Vcellopt; +import org.vcell.util.document.KeyValue; public record OptimizationJobStatus( - String id, + KeyValue id, OptJobStatus status, String statusMessage, String htcJobId, diff --git a/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java b/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java index c81613e036..7c41871ea0 100644 --- a/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java +++ b/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java @@ -4,6 +4,7 @@ import jakarta.enterprise.context.ApplicationScoped; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.vcell.db.KeyFactory; import org.vcell.optimization.CopasiUtils; import org.vcell.optimization.jtd.OptProblem; import org.vcell.optimization.jtd.OptProgressReport; @@ -12,23 +13,25 @@ import org.vcell.restq.models.OptJobStatus; import org.vcell.restq.models.OptimizationJobStatus; import org.vcell.util.DataAccessException; +import org.vcell.util.document.KeyValue; import org.vcell.util.document.User; import java.io.File; import java.io.IOException; import java.sql.*; import java.time.Instant; -import java.util.UUID; @ApplicationScoped public class OptimizationRestService { private static final Logger lg = LogManager.getLogger(OptimizationRestService.class); private final AgroalConnectionFactory connectionFactory; + private final KeyFactory keyFactory; private final ObjectMapper objectMapper; public OptimizationRestService(AgroalConnectionFactory connectionFactory) { this.connectionFactory = connectionFactory; + this.keyFactory = connectionFactory.getKeyFactory(); this.objectMapper = new ObjectMapper(); } @@ -42,42 +45,49 @@ public OptimizationRestService(AgroalConnectionFactory connectionFactory) { */ public OptimizationJobStatus submitOptimization(OptProblem optProblem, File parestDataDir, User user) throws SQLException, IOException { - String jobId = UUID.randomUUID().toString(); - String filePrefix = "CopasiParest_" + jobId; + Connection con = null; + try { + con = connectionFactory.getConnection(this); + KeyValue jobKey = keyFactory.getNewKey(con); + String filePrefix = "CopasiParest_" + jobKey; - File optProblemFile = new File(parestDataDir, filePrefix + "_optProblem.json"); - File optOutputFile = new File(parestDataDir, filePrefix + "_optRun.json"); - File optReportFile = new File(parestDataDir, filePrefix + "_optReport.txt"); + File optProblemFile = new File(parestDataDir, filePrefix + "_optProblem.json"); + File optOutputFile = new File(parestDataDir, filePrefix + "_optRun.json"); + File optReportFile = new File(parestDataDir, filePrefix + "_optReport.txt"); - // Write the OptProblem to the filesystem - if (!parestDataDir.exists()) { - parestDataDir.mkdirs(); - } - CopasiUtils.writeOptProblem(optProblemFile, optProblem); + // Write the OptProblem to the filesystem + if (!parestDataDir.exists()) { + parestDataDir.mkdirs(); + } + CopasiUtils.writeOptProblem(optProblemFile, optProblem); - // Insert the database record - Instant now = Instant.now(); - insertOptJob(jobId, user, OptJobStatus.SUBMITTED, - optProblemFile.getAbsolutePath(), optOutputFile.getAbsolutePath(), optReportFile.getAbsolutePath(), - null, null, now); + // Insert the database record + Instant now = Instant.now(); + insertOptJob(con, jobKey, user, OptJobStatus.SUBMITTED, + optProblemFile.getAbsolutePath(), optOutputFile.getAbsolutePath(), optReportFile.getAbsolutePath(), + null, null, now); + con.commit(); - return new OptimizationJobStatus(jobId, OptJobStatus.SUBMITTED, null, null, null, null); + return new OptimizationJobStatus(jobKey, OptJobStatus.SUBMITTED, null, null, null, null); + } catch (SQLException | IOException e) { + if (con != null) con.rollback(); + throw e; + } finally { + if (con != null) connectionFactory.release(con, this); + } } /** * Get the current status of an optimization job, including progress or results if available. */ - public OptimizationJobStatus getOptimizationStatus(String jobId, User user) + public OptimizationJobStatus getOptimizationStatus(KeyValue jobKey, User user) throws SQLException, DataAccessException, IOException { - OptJobRecord record = getOptJobRecord(jobId); + OptJobRecord record = getOptJobRecord(jobKey); if (record == null) { - throw new DataAccessException("Optimization job not found: " + jobId); + throw new DataAccessException("Optimization job not found: " + jobKey); } - if (record.ownerRef != user.getID().toString().hashCode() && !record.ownerUserid.equals(user.getName())) { - // check actual ownership via user key - if (!Long.toString(record.ownerRef).equals(user.getID().toString())) { - throw new DataAccessException("Not authorized to access optimization job: " + jobId); - } + if (!record.ownerKey.equals(user.getID())) { + throw new DataAccessException("Not authorized to access optimization job: " + jobKey); } OptProgressReport progressReport = null; @@ -91,8 +101,8 @@ public OptimizationJobStatus getOptimizationStatus(String jobId, User user) // Check if the output file has appeared (solver completed) results = readResults(record.optOutputFile); if (results != null) { - updateOptJobStatus(jobId, OptJobStatus.COMPLETE, null); - return new OptimizationJobStatus(jobId, OptJobStatus.COMPLETE, null, record.htcJobId, progressReport, results); + updateOptJobStatus(jobKey, OptJobStatus.COMPLETE, null); + return new OptimizationJobStatus(jobKey, OptJobStatus.COMPLETE, null, record.htcJobId, progressReport, results); } break; case COMPLETE: @@ -109,13 +119,13 @@ public OptimizationJobStatus getOptimizationStatus(String jobId, User user) break; } - return new OptimizationJobStatus(jobId, record.status, record.statusMessage, record.htcJobId, progressReport, results); + return new OptimizationJobStatus(jobKey, record.status, record.statusMessage, record.htcJobId, progressReport, results); } /** * Update the status of an optimization job (e.g. when vcell-submit reports back). */ - public void updateOptJobStatus(String jobId, OptJobStatus status, String statusMessage) + public void updateOptJobStatus(KeyValue jobKey, OptJobStatus status, String statusMessage) throws SQLException { Connection con = null; try { @@ -125,7 +135,7 @@ public void updateOptJobStatus(String jobId, OptJobStatus status, String statusM stmt.setString(1, status.name()); stmt.setString(2, statusMessage); stmt.setTimestamp(3, Timestamp.from(Instant.now())); - stmt.setString(4, jobId); + stmt.setLong(4, Long.parseLong(jobKey.toString())); stmt.executeUpdate(); con.commit(); } catch (SQLException e) { @@ -139,7 +149,7 @@ public void updateOptJobStatus(String jobId, OptJobStatus status, String statusM /** * Update the SLURM job ID after vcell-submit confirms submission. */ - public void updateHtcJobId(String jobId, String htcJobId) throws SQLException { + public void updateHtcJobId(KeyValue jobKey, String htcJobId) throws SQLException { Connection con = null; try { con = connectionFactory.getConnection(this); @@ -148,7 +158,7 @@ public void updateHtcJobId(String jobId, String htcJobId) throws SQLException { stmt.setString(1, htcJobId); stmt.setString(2, OptJobStatus.QUEUED.name()); stmt.setTimestamp(3, Timestamp.from(Instant.now())); - stmt.setString(4, jobId); + stmt.setLong(4, Long.parseLong(jobKey.toString())); stmt.executeUpdate(); con.commit(); } catch (SQLException e) { @@ -162,60 +172,49 @@ public void updateHtcJobId(String jobId, String htcJobId) throws SQLException { /** * Get the SLURM job ID for a given optimization job (needed for stop). */ - public String getHtcJobId(String jobId) throws SQLException, DataAccessException { - OptJobRecord record = getOptJobRecord(jobId); + public String getHtcJobId(KeyValue jobKey) throws SQLException, DataAccessException { + OptJobRecord record = getOptJobRecord(jobKey); if (record == null) { - throw new DataAccessException("Optimization job not found: " + jobId); + throw new DataAccessException("Optimization job not found: " + jobKey); } return record.htcJobId; } // --- Private helpers --- - private void insertOptJob(String jobId, User user, OptJobStatus status, + private void insertOptJob(Connection con, KeyValue jobKey, User user, OptJobStatus status, String optProblemFile, String optOutputFile, String optReportFile, String htcJobId, String statusMessage, Instant now) throws SQLException { - Connection con = null; - try { - con = connectionFactory.getConnection(this); - PreparedStatement stmt = con.prepareStatement( - "INSERT INTO vc_optjob (id, ownerRef, status, optProblemFile, optOutputFile, optReportFile, " + - "htcJobId, statusMessage, insertDate, updateDate) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - stmt.setString(1, jobId); - stmt.setLong(2, Long.parseLong(user.getID().toString())); - stmt.setString(3, status.name()); - stmt.setString(4, optProblemFile); - stmt.setString(5, optOutputFile); - stmt.setString(6, optReportFile); - stmt.setString(7, htcJobId); - stmt.setString(8, statusMessage); - stmt.setTimestamp(9, Timestamp.from(now)); - stmt.setTimestamp(10, Timestamp.from(now)); - stmt.executeUpdate(); - con.commit(); - } catch (SQLException e) { - if (con != null) con.rollback(); - throw e; - } finally { - if (con != null) connectionFactory.release(con, this); - } + PreparedStatement stmt = con.prepareStatement( + "INSERT INTO vc_optjob (id, ownerRef, status, optProblemFile, optOutputFile, optReportFile, " + + "htcJobId, statusMessage, insertDate, updateDate) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + stmt.setLong(1, Long.parseLong(jobKey.toString())); + stmt.setLong(2, Long.parseLong(user.getID().toString())); + stmt.setString(3, status.name()); + stmt.setString(4, optProblemFile); + stmt.setString(5, optOutputFile); + stmt.setString(6, optReportFile); + stmt.setString(7, htcJobId); + stmt.setString(8, statusMessage); + stmt.setTimestamp(9, Timestamp.from(now)); + stmt.setTimestamp(10, Timestamp.from(now)); + stmt.executeUpdate(); } - private OptJobRecord getOptJobRecord(String jobId) throws SQLException { + private OptJobRecord getOptJobRecord(KeyValue jobKey) throws SQLException { Connection con = null; try { con = connectionFactory.getConnection(this); PreparedStatement stmt = con.prepareStatement( "SELECT j.id, j.ownerRef, j.status, j.optProblemFile, j.optOutputFile, j.optReportFile, " + - "j.htcJobId, j.statusMessage, j.insertDate, j.updateDate, u.userid " + - "FROM vc_optjob j JOIN vc_userinfo u ON j.ownerRef = u.id WHERE j.id = ?"); - stmt.setString(1, jobId); + "j.htcJobId, j.statusMessage, j.insertDate, j.updateDate " + + "FROM vc_optjob j WHERE j.id = ?"); + stmt.setLong(1, Long.parseLong(jobKey.toString())); ResultSet rs = stmt.executeQuery(); if (rs.next()) { return new OptJobRecord( - rs.getString("id"), - rs.getLong("ownerRef"), - rs.getString("userid"), + new KeyValue(rs.getBigDecimal("id")), + new KeyValue(rs.getBigDecimal("ownerRef")), OptJobStatus.valueOf(rs.getString("status")), rs.getString("optProblemFile"), rs.getString("optOutputFile"), @@ -260,9 +259,8 @@ private Vcellopt readResults(String outputFilePath) { * Internal record for database row data. */ record OptJobRecord( - String id, - long ownerRef, - String ownerUserid, + KeyValue id, + KeyValue ownerKey, OptJobStatus status, String optProblemFile, String optOutputFile, diff --git a/vcell-rest/src/main/resources/scripts/init.sql b/vcell-rest/src/main/resources/scripts/init.sql index a7ff0062d7..87cdcda404 100644 --- a/vcell-rest/src/main/resources/scripts/init.sql +++ b/vcell-rest/src/main/resources/scripts/init.sql @@ -76,7 +76,7 @@ CREATE TABLE vc_userlogininfo(id bigint PRIMARY KEY,userRef bigint NOT NULL REFE CREATE TABLE vc_metadata(id bigint PRIMARY KEY,bioModelRef bigint NOT NULL REFERENCES vc_biomodel(id) ON DELETE CASCADE,vcMetaDataLarge text ,vcMetaDataSmall varchar(4000) ); CREATE TABLE vc_simdelfromdisk(deldate varchar(20) ,userid varchar(255) NOT NULL,userkey bigint ,simid bigint ,simpref bigint ,simdate varchar(20) ,simname varchar(255) NOT NULL,status varchar(10) ,numfiles bigint ,totalsize bigint ); CREATE TABLE vc_useridentity(id bigint PRIMARY KEY,userRef bigint NOT NULL REFERENCES vc_userinfo(id),authSubject varchar(128) NOT NULL,authIssuer varchar(128) NOT NULL,insertDate timestamp NOT NULL); -CREATE TABLE vc_optjob(id varchar(64) PRIMARY KEY,ownerRef bigint NOT NULL REFERENCES vc_userinfo(id),status varchar(32) NOT NULL,optProblemFile varchar(512) NOT NULL,optOutputFile varchar(512) NOT NULL,optReportFile varchar(512) NOT NULL,htcJobId varchar(128),statusMessage varchar(4000),insertDate timestamp NOT NULL,updateDate timestamp NOT NULL); +CREATE TABLE vc_optjob(id bigint PRIMARY KEY,ownerRef bigint NOT NULL REFERENCES vc_userinfo(id),status varchar(32) NOT NULL,optProblemFile varchar(512) NOT NULL,optOutputFile varchar(512) NOT NULL,optReportFile varchar(512) NOT NULL,htcJobId varchar(128),statusMessage varchar(4000),insertDate timestamp NOT NULL,updateDate timestamp NOT NULL); CREATE VIEW public.dual AS SELECT CAST('X' as varchar) AS dummy; From 2c328d3551409fa6b278a81978cf0d9b322bad22 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Wed, 8 Apr 2026 11:47:51 -0400 Subject: [PATCH 05/40] Add GET /api/v1/optimization endpoint to list user's optimization jobs Returns lightweight job metadata (id, status, htcJobId, statusMessage) without the heavy progressReport/results fields. Most recent first. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../restq/handlers/OptimizationResource.java | 12 ++++++++ .../services/OptimizationRestService.java | 29 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java b/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java index 44c04647f1..e35eaa3446 100644 --- a/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java +++ b/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java @@ -43,6 +43,18 @@ public OptimizationResource(OptimizationRestService optimizationRestService) { this.optimizationRestService = optimizationRestService; } + @GET + @RolesAllowed("user") + @Operation(operationId = "listOptimizationJobs", summary = "List optimization jobs for the authenticated user") + public OptimizationJobStatus[] list() throws NotAuthenticatedWebException, DataAccessWebException { + try { + User vcellUser = userRestService.getUserFromIdentity(securityIdentity); + return optimizationRestService.listOptimizationJobs(vcellUser); + } catch (SQLException e) { + throw new RuntimeWebException("Failed to list optimization jobs: " + e.getMessage(), e); + } + } + @POST @Consumes(MediaType.APPLICATION_JSON) @RolesAllowed("user") diff --git a/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java b/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java index 7c41871ea0..035c454331 100644 --- a/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java +++ b/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java @@ -180,6 +180,35 @@ public String getHtcJobId(KeyValue jobKey) throws SQLException, DataAccessExcept return record.htcJobId; } + /** + * List optimization jobs for a user, most recent first. Returns lightweight status (no progress/results). + */ + public OptimizationJobStatus[] listOptimizationJobs(User user) throws SQLException { + Connection con = null; + try { + con = connectionFactory.getConnection(this); + PreparedStatement stmt = con.prepareStatement( + "SELECT id, status, htcJobId, statusMessage, insertDate, updateDate " + + "FROM vc_optjob WHERE ownerRef = ? ORDER BY insertDate DESC"); + stmt.setLong(1, Long.parseLong(user.getID().toString())); + ResultSet rs = stmt.executeQuery(); + java.util.List jobs = new java.util.ArrayList<>(); + while (rs.next()) { + jobs.add(new OptimizationJobStatus( + new KeyValue(rs.getBigDecimal("id")), + OptJobStatus.valueOf(rs.getString("status")), + rs.getString("statusMessage"), + rs.getString("htcJobId"), + null, + null + )); + } + return jobs.toArray(new OptimizationJobStatus[0]); + } finally { + if (con != null) connectionFactory.release(con, this); + } + } + // --- Private helpers --- private void insertOptJob(Connection con, KeyValue jobKey, User user, OptJobStatus status, From 2ddc7789a4e7d269b3abf73ca91f8130e52930ee Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Wed, 8 Apr 2026 11:55:24 -0400 Subject: [PATCH 06/40] Add ActiveMQ messaging for optimization job dispatch and status - Add OptimizationMQ with producer (opt-request) and consumer (opt-status) - Producer sends submit/stop commands to vcell-submit via AMQP - Consumer receives status updates (QUEUED/htcJobId, FAILED/error) and updates the database accordingly - Wire messaging into OptimizationResource submit and stop endpoints - Add AMQP channel configuration to application.properties (test profile) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../vcell/restq/activemq/OptimizationMQ.java | 109 ++++++++++++++++++ .../restq/handlers/OptimizationResource.java | 26 ++++- .../src/main/resources/application.properties | 6 + 3 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 vcell-rest/src/main/java/org/vcell/restq/activemq/OptimizationMQ.java diff --git a/vcell-rest/src/main/java/org/vcell/restq/activemq/OptimizationMQ.java b/vcell-rest/src/main/java/org/vcell/restq/activemq/OptimizationMQ.java new file mode 100644 index 0000000000..db1eb20ebe --- /dev/null +++ b/vcell-rest/src/main/java/org/vcell/restq/activemq/OptimizationMQ.java @@ -0,0 +1,109 @@ +package org.vcell.restq.activemq; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.smallrye.mutiny.Uni; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.microprofile.reactive.messaging.Channel; +import org.eclipse.microprofile.reactive.messaging.Emitter; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.vcell.restq.models.OptJobStatus; +import org.vcell.restq.services.OptimizationRestService; +import org.vcell.util.document.KeyValue; + +import java.sql.SQLException; + +/** + * ActiveMQ messaging for optimization job dispatch and status updates. + * + * vcell-rest produces messages on "opt-request" (submit/stop commands to vcell-submit) + * and consumes messages on "opt-status" (status updates from vcell-submit). + */ +@ApplicationScoped +public class OptimizationMQ { + private static final Logger lg = LogManager.getLogger(OptimizationMQ.class); + + @Inject + ObjectMapper mapper; + + @Inject + OptimizationRestService optimizationRestService; + + @Inject + @Channel("publisher-opt-request") + Emitter optRequestEmitter; + + /** + * Send a submit command to vcell-submit. + */ + public void sendSubmitRequest(OptRequestMessage request) { + try { + lg.info("Sending optimization submit request for job {}", request.jobId()); + optRequestEmitter.send(mapper.writeValueAsString(request)); + } catch (Exception e) { + lg.error("Failed to send optimization submit request for job {}: {}", request.jobId(), e.getMessage(), e); + } + } + + /** + * Send a stop command to vcell-submit. + */ + public void sendStopRequest(OptRequestMessage request) { + try { + lg.info("Sending optimization stop request for job {}", request.jobId()); + optRequestEmitter.send(mapper.writeValueAsString(request)); + } catch (Exception e) { + lg.error("Failed to send optimization stop request for job {}: {}", request.jobId(), e.getMessage(), e); + } + } + + /** + * Consume status updates from vcell-submit (e.g. QUEUED with htcJobId, FAILED with error). + */ + @Incoming("subscriber-opt-status") + public Uni consumeOptStatus(Message message) { + try { + OptStatusMessage statusMsg = mapper.readValue(message.getPayload(), OptStatusMessage.class); + lg.info("Received optimization status update: job={}, status={}", statusMsg.jobId(), statusMsg.status()); + + KeyValue jobKey = new KeyValue(statusMsg.jobId()); + + if (statusMsg.htcJobId() != null) { + optimizationRestService.updateHtcJobId(jobKey, statusMsg.htcJobId()); + } + if (statusMsg.status() != null) { + optimizationRestService.updateOptJobStatus(jobKey, statusMsg.status(), statusMsg.statusMessage()); + } + + return Uni.createFrom().completionStage(message.ack()); + } catch (Exception e) { + lg.error("Failed to process optimization status message: {}", e.getMessage(), e); + return Uni.createFrom().completionStage(message.nack(e)); + } + } + + /** + * Message sent from vcell-rest to vcell-submit to request job submission or stop. + */ + public record OptRequestMessage( + String jobId, + String command, // "submit" or "stop" + String optProblemFilePath, // for submit + String optOutputFilePath, // for submit + String optReportFilePath, // for submit + String htcJobId // for stop (SLURM job to cancel) + ) {} + + /** + * Message sent from vcell-submit back to vcell-rest with status updates. + */ + public record OptStatusMessage( + String jobId, + OptJobStatus status, + String statusMessage, + String htcJobId // set when SLURM job is submitted + ) {} +} diff --git a/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java b/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java index e35eaa3446..971548a8c2 100644 --- a/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java +++ b/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java @@ -7,6 +7,7 @@ import jakarta.ws.rs.core.MediaType; import org.eclipse.microprofile.openapi.annotations.Operation; import org.vcell.optimization.jtd.OptProblem; +import org.vcell.restq.activemq.OptimizationMQ; import org.vcell.restq.errors.exceptions.DataAccessWebException; import org.vcell.restq.errors.exceptions.NotAuthenticatedWebException; import org.vcell.restq.errors.exceptions.NotFoundWebException; @@ -35,6 +36,9 @@ public class OptimizationResource { @Inject UserRestService userRestService; + @Inject + OptimizationMQ optimizationMQ; + // TODO: make configurable via application.properties private static final String PAREST_DATA_DIR = "/simdata/parest_data"; @@ -65,7 +69,16 @@ public OptimizationJobStatus submit(OptProblem optProblem) User vcellUser = userRestService.getUserFromIdentity(securityIdentity); OptimizationJobStatus status = optimizationRestService.submitOptimization( optProblem, new File(PAREST_DATA_DIR), vcellUser); - // TODO: send ActiveMQ message to vcell-submit for SLURM dispatch (commit 3) + + optimizationMQ.sendSubmitRequest(new OptimizationMQ.OptRequestMessage( + status.id().toString(), + "submit", + new File(PAREST_DATA_DIR, "CopasiParest_" + status.id() + "_optProblem.json").getAbsolutePath(), + new File(PAREST_DATA_DIR, "CopasiParest_" + status.id() + "_optRun.json").getAbsolutePath(), + new File(PAREST_DATA_DIR, "CopasiParest_" + status.id() + "_optReport.txt").getAbsolutePath(), + null + )); + return status; } catch (SQLException e) { throw new RuntimeWebException("Failed to submit optimization job: " + e.getMessage(), e); @@ -101,12 +114,19 @@ public OptimizationJobStatus stop(@PathParam("optId") Long optId) try { User vcellUser = userRestService.getUserFromIdentity(securityIdentity); KeyValue jobKey = new KeyValue(optId.toString()); - // Verify ownership by fetching status (throws if not authorized) OptimizationJobStatus currentStatus = optimizationRestService.getOptimizationStatus(jobKey, vcellUser); if (currentStatus.status() != OptJobStatus.RUNNING && currentStatus.status() != OptJobStatus.QUEUED) { throw new DataAccessWebException("Cannot stop optimization job in state: " + currentStatus.status()); } - // TODO: send ActiveMQ stop message to vcell-submit with htcJobId (commit 3) + + String htcJobId = optimizationRestService.getHtcJobId(jobKey); + optimizationMQ.sendStopRequest(new OptimizationMQ.OptRequestMessage( + jobKey.toString(), + "stop", + null, null, null, + htcJobId + )); + optimizationRestService.updateOptJobStatus(jobKey, OptJobStatus.STOPPED, "Stopped by user"); return optimizationRestService.getOptimizationStatus(jobKey, vcellUser); } catch (DataAccessException e) { diff --git a/vcell-rest/src/main/resources/application.properties b/vcell-rest/src/main/resources/application.properties index 932fd8285f..30c69b3bd7 100644 --- a/vcell-rest/src/main/resources/application.properties +++ b/vcell-rest/src/main/resources/application.properties @@ -179,6 +179,12 @@ quarkus.swagger-ui.always-include=true %test.mp.messaging.outgoing.publisher-client-status.connect=smallrye-amqp %test.mp.messaging.outgoing.publisher-client-status.address=client-status +%test.mp.messaging.outgoing.publisher-opt-request.connector=smallrye-amqp +%test.mp.messaging.outgoing.publisher-opt-request.address=opt-request + +%test.mp.messaging.incoming.subscriber-opt-status.connector=smallrye-amqp +%test.mp.messaging.incoming.subscriber-opt-status.address=opt-status + quarkus.amqp.devservices.enabled=false vcell.exporter=false From c748e86a083f183b1cb2e878e3367ca7bef92a61 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Wed, 8 Apr 2026 12:09:51 -0400 Subject: [PATCH 07/40] Add JMS queue listener for optimization requests on vcell-submit side MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OptimizationBatchServer.initOptimizationQueue() creates a JMS consumer on the "opt-request" queue (activemqint broker). Cross-protocol with vcell-rest's SmallRye AMQP producer — ActiveMQ bridges AMQP 1.0 and OpenWire transparently on the same queue name. On "submit": reads OptProblem from NFS, submits SLURM job via SlurmProxy, sends QUEUED status back on "opt-status" with htcJobId. On "stop": parses htcJobId and calls killJobSafe() for scancel. Message format: plain JSON text matching OptimizationMQ records in vcell-rest. Uses mutable POJOs (not records) for Jackson compatibility with the vcell-server Java 17 codebase. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../batch/opt/OptimizationBatchServer.java | 165 ++++++++++++++++++ .../server/batch/sim/HtcSimulationWorker.java | 5 + 2 files changed, 170 insertions(+) diff --git a/vcell-server/src/main/java/cbit/vcell/message/server/batch/opt/OptimizationBatchServer.java b/vcell-server/src/main/java/cbit/vcell/message/server/batch/opt/OptimizationBatchServer.java index 3ad7f923f8..83cdbfc928 100644 --- a/vcell-server/src/main/java/cbit/vcell/message/server/batch/opt/OptimizationBatchServer.java +++ b/vcell-server/src/main/java/cbit/vcell/message/server/batch/opt/OptimizationBatchServer.java @@ -14,6 +14,9 @@ import org.vcell.optimization.jtd.Vcellopt; import org.vcell.util.exe.ExecutableException; +import javax.jms.*; +import org.apache.activemq.ActiveMQConnectionFactory; + import java.io.*; import java.net.ServerSocket; import java.net.Socket; @@ -214,6 +217,168 @@ public void run() { optThread.start(); } + /** + * Initialize a JMS queue listener on "opt-request" for cross-protocol messaging with vcell-rest (AMQP 1.0). + * Receives submit/stop commands as JSON text messages, dispatches to SLURM, and sends status updates + * back on "opt-status". + */ + public void initOptimizationQueue(String jmsHost, int jmsPort) { + Thread optQueueThread = new Thread(() -> { + ObjectMapper objectMapper = new ObjectMapper(); + try { + ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( + "tcp://" + jmsHost + ":" + jmsPort); + connectionFactory.setTrustAllPackages(true); + Connection connection = connectionFactory.createConnection(); + connection.start(); + + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + Destination requestQueue = session.createQueue("opt-request"); + Destination statusQueue = session.createQueue("opt-status"); + MessageConsumer consumer = session.createConsumer(requestQueue); + MessageProducer producer = session.createProducer(statusQueue); + + lg.info("Optimization JMS queue listener started on opt-request"); + + while (true) { + Message message = consumer.receive(2000); // 2 second poll + if (message == null) continue; + + if (message instanceof TextMessage textMessage) { + try { + String json = textMessage.getText(); + OptRequestMessage request = objectMapper.readValue(json, OptRequestMessage.class); + lg.info("Received optimization request: command={}, jobId={}", request.command, request.jobId); + + if ("submit".equals(request.command)) { + handleSubmitRequest(request, session, producer, objectMapper); + } else if ("stop".equals(request.command)) { + handleStopRequest(request); + } else { + lg.warn("Unknown optimization command: {}", request.command); + } + } catch (Exception e) { + lg.error("Error processing optimization request: {}", e.getMessage(), e); + } + } + } + } catch (Exception e) { + lg.error("Optimization JMS queue listener failed: {}", e.getMessage(), e); + } + }, "optQueueListener"); + optQueueThread.setDaemon(true); + optQueueThread.start(); + } + + private void handleSubmitRequest(OptRequestMessage request, Session session, + MessageProducer producer, ObjectMapper objectMapper) { + try { + // The OptProblem file is already written by vcell-rest — read it + File optProblemFile = new File(request.optProblemFilePath); + OptProblem optProblem = objectMapper.readValue(optProblemFile, OptProblem.class); + + HtcProxy htcProxyClone = getHtcProxy().cloneThreadsafe(); + File htcLogDirExternal = new File(PropertyLoader.getRequiredProperty(PropertyLoader.htcLogDirExternal)); + File htcLogDirInternal = new File(PropertyLoader.getRequiredProperty(PropertyLoader.htcLogDirInternal)); + String slurmOptJobName = "CopasiParest_" + request.jobId; + String optSubFileName = slurmOptJobName + ".sub"; + File sub_file_external = new File(htcLogDirExternal, optSubFileName); + File sub_file_internal = new File(htcLogDirInternal, optSubFileName); + + File optOutputFile = new File(request.optOutputFilePath); + File optReportFile = new File(request.optReportFilePath); + + HtcJobID htcJobID = htcProxyClone.submitOptimizationJob( + slurmOptJobName, sub_file_internal, sub_file_external, + optProblemFile, optOutputFile, optReportFile); + + lg.info("Submitted SLURM job {} for optimization jobId={}", htcJobID, request.jobId); + + // Send QUEUED status back with htcJobId + sendStatusMessage(session, producer, objectMapper, + request.jobId, "QUEUED", null, htcJobID.toDatabase()); + } catch (Exception e) { + lg.error("Failed to submit optimization job {}: {}", request.jobId, e.getMessage(), e); + try { + sendStatusMessage(session, producer, objectMapper, + request.jobId, "FAILED", e.getMessage(), null); + } catch (JMSException jmsEx) { + lg.error("Failed to send FAILED status for job {}: {}", request.jobId, jmsEx.getMessage(), jmsEx); + } + } + } + + private void handleStopRequest(OptRequestMessage request) { + if (request.htcJobId == null) { + lg.warn("Cannot stop optimization job {} — no htcJobId", request.jobId); + return; + } + try { + HtcProxy htcProxyClone = getHtcProxy().cloneThreadsafe(); + // htcJobId is in toDatabase() format: "SLURM:12345" or "SLURM:12345.server" + String htcJobIdStr = request.htcJobId; + HtcJobID htcJobID; + if (htcJobIdStr.startsWith("SLURM:")) { + htcJobID = new HtcJobID(htcJobIdStr.substring("SLURM:".length()), HtcJobID.BatchSystemType.SLURM); + } else { + htcJobID = new HtcJobID(htcJobIdStr, HtcJobID.BatchSystemType.SLURM); + } + String jobName = "CopasiParest_" + request.jobId; + htcProxyClone.killJobSafe(new HtcProxy.HtcJobInfo(htcJobID, jobName)); + lg.info("Stopped SLURM job {} for optimization jobId={}", request.htcJobId, request.jobId); + } catch (Exception e) { + lg.error("Failed to stop optimization job {}: {}", request.jobId, e.getMessage(), e); + } + } + + private void sendStatusMessage(Session session, MessageProducer producer, ObjectMapper objectMapper, + String jobId, String status, String statusMessage, String htcJobId) + throws JMSException { + try { + OptStatusMessage statusMsg = new OptStatusMessage(jobId, status, statusMessage, htcJobId); + String json = objectMapper.writeValueAsString(statusMsg); + TextMessage textMessage = session.createTextMessage(json); + producer.send(textMessage); + lg.info("Sent optimization status: jobId={}, status={}", jobId, status); + } catch (Exception e) { + lg.error("Failed to send status message for job {}: {}", jobId, e.getMessage(), e); + throw new JMSException("Failed to serialize status message: " + e.getMessage()); + } + } + + /** + * Message from vcell-rest requesting job submission or stop. + * Must match the JSON format produced by OptimizationMQ.OptRequestMessage in vcell-rest. + */ + public static class OptRequestMessage { + public String jobId; + public String command; + public String optProblemFilePath; + public String optOutputFilePath; + public String optReportFilePath; + public String htcJobId; + } + + /** + * Status message sent back to vcell-rest. + * Must match the JSON format expected by OptimizationMQ.OptStatusMessage in vcell-rest. + */ + public static class OptStatusMessage { + public String jobId; + public String status; + public String statusMessage; + public String htcJobId; + + public OptStatusMessage() {} + + public OptStatusMessage(String jobId, String status, String statusMessage, String htcJobId) { + this.jobId = jobId; + this.status = status; + this.statusMessage = statusMessage; + this.htcJobId = htcJobId; + } + } + private Vcellopt getOptResults(String optID) throws IOException { File f = generateOptOutputFilePath(optID); if (f.exists()) {// opt job done diff --git a/vcell-server/src/main/java/cbit/vcell/message/server/batch/sim/HtcSimulationWorker.java b/vcell-server/src/main/java/cbit/vcell/message/server/batch/sim/HtcSimulationWorker.java index 44503e2ca4..9afe73413b 100644 --- a/vcell-server/src/main/java/cbit/vcell/message/server/batch/sim/HtcSimulationWorker.java +++ b/vcell-server/src/main/java/cbit/vcell/message/server/batch/sim/HtcSimulationWorker.java @@ -111,6 +111,11 @@ public final String getJobSelector() { public void init() { initQueueConsumer(); optimizationBatchServer.initOptimizationSocket(); + + // Start JMS queue listener for optimization requests from vcell-rest (AMQP cross-protocol) + String jmshost_int = PropertyLoader.getRequiredProperty(PropertyLoader.jmsIntHostInternal); + int jmsport_int = Integer.parseInt(PropertyLoader.getRequiredProperty(PropertyLoader.jmsIntPortInternal)); + optimizationBatchServer.initOptimizationQueue(jmshost_int, jmsport_int); } private static class PostProcessingChores { From 2e6211eb4b3f2436deeaaf9ddcd0f6b3e7ea7364 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Wed, 8 Apr 2026 12:20:06 -0400 Subject: [PATCH 08/40] Consolidate optimization message types into vcell-core Move OptJobStatus, OptRequestMessage, and OptStatusMessage from duplicate definitions in vcell-rest and vcell-server into org.vcell.optimization in vcell-core. Both modules now share a single source of truth for the cross-protocol messaging contract. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../org/vcell/optimization}/OptJobStatus.java | 2 +- .../vcell/optimization/OptRequestMessage.java | 26 ++++++++++ .../vcell/optimization/OptStatusMessage.java | 21 +++++++++ .../vcell/restq/activemq/OptimizationMQ.java | 47 +++++-------------- .../restq/handlers/OptimizationResource.java | 7 +-- .../restq/models/OptimizationJobStatus.java | 1 + .../services/OptimizationRestService.java | 2 +- .../batch/opt/OptimizationBatchServer.java | 42 +++-------------- 8 files changed, 72 insertions(+), 76 deletions(-) rename {vcell-rest/src/main/java/org/vcell/restq/models => vcell-core/src/main/java/org/vcell/optimization}/OptJobStatus.java (77%) create mode 100644 vcell-core/src/main/java/org/vcell/optimization/OptRequestMessage.java create mode 100644 vcell-core/src/main/java/org/vcell/optimization/OptStatusMessage.java diff --git a/vcell-rest/src/main/java/org/vcell/restq/models/OptJobStatus.java b/vcell-core/src/main/java/org/vcell/optimization/OptJobStatus.java similarity index 77% rename from vcell-rest/src/main/java/org/vcell/restq/models/OptJobStatus.java rename to vcell-core/src/main/java/org/vcell/optimization/OptJobStatus.java index 3d2dede18d..ee1dbcb4e4 100644 --- a/vcell-rest/src/main/java/org/vcell/restq/models/OptJobStatus.java +++ b/vcell-core/src/main/java/org/vcell/optimization/OptJobStatus.java @@ -1,4 +1,4 @@ -package org.vcell.restq.models; +package org.vcell.optimization; public enum OptJobStatus { SUBMITTED, diff --git a/vcell-core/src/main/java/org/vcell/optimization/OptRequestMessage.java b/vcell-core/src/main/java/org/vcell/optimization/OptRequestMessage.java new file mode 100644 index 0000000000..33e974349b --- /dev/null +++ b/vcell-core/src/main/java/org/vcell/optimization/OptRequestMessage.java @@ -0,0 +1,26 @@ +package org.vcell.optimization; + +/** + * Internal message sent from vcell-rest to vcell-submit to request job submission or stop. + * Serialized as JSON over ActiveMQ (AMQP 1.0 on vcell-rest side, JMS/OpenWire on vcell-submit side). + */ +public class OptRequestMessage { + public String jobId; + public String command; // "submit" or "stop" + public String optProblemFilePath; // for submit + public String optOutputFilePath; // for submit + public String optReportFilePath; // for submit + public String htcJobId; // for stop (SLURM job to cancel) + + public OptRequestMessage() {} + + public OptRequestMessage(String jobId, String command, String optProblemFilePath, + String optOutputFilePath, String optReportFilePath, String htcJobId) { + this.jobId = jobId; + this.command = command; + this.optProblemFilePath = optProblemFilePath; + this.optOutputFilePath = optOutputFilePath; + this.optReportFilePath = optReportFilePath; + this.htcJobId = htcJobId; + } +} diff --git a/vcell-core/src/main/java/org/vcell/optimization/OptStatusMessage.java b/vcell-core/src/main/java/org/vcell/optimization/OptStatusMessage.java new file mode 100644 index 0000000000..d10e8e45b4 --- /dev/null +++ b/vcell-core/src/main/java/org/vcell/optimization/OptStatusMessage.java @@ -0,0 +1,21 @@ +package org.vcell.optimization; + +/** + * Internal message sent from vcell-submit back to vcell-rest with status updates. + * Serialized as JSON over ActiveMQ (JMS/OpenWire on vcell-submit side, AMQP 1.0 on vcell-rest side). + */ +public class OptStatusMessage { + public String jobId; + public OptJobStatus status; + public String statusMessage; + public String htcJobId; // set when SLURM job is submitted + + public OptStatusMessage() {} + + public OptStatusMessage(String jobId, OptJobStatus status, String statusMessage, String htcJobId) { + this.jobId = jobId; + this.status = status; + this.statusMessage = statusMessage; + this.htcJobId = htcJobId; + } +} diff --git a/vcell-rest/src/main/java/org/vcell/restq/activemq/OptimizationMQ.java b/vcell-rest/src/main/java/org/vcell/restq/activemq/OptimizationMQ.java index db1eb20ebe..610ff6c188 100644 --- a/vcell-rest/src/main/java/org/vcell/restq/activemq/OptimizationMQ.java +++ b/vcell-rest/src/main/java/org/vcell/restq/activemq/OptimizationMQ.java @@ -10,12 +10,11 @@ import org.eclipse.microprofile.reactive.messaging.Emitter; import org.eclipse.microprofile.reactive.messaging.Incoming; import org.eclipse.microprofile.reactive.messaging.Message; -import org.vcell.restq.models.OptJobStatus; +import org.vcell.optimization.OptRequestMessage; +import org.vcell.optimization.OptStatusMessage; import org.vcell.restq.services.OptimizationRestService; import org.vcell.util.document.KeyValue; -import java.sql.SQLException; - /** * ActiveMQ messaging for optimization job dispatch and status updates. * @@ -41,10 +40,10 @@ public class OptimizationMQ { */ public void sendSubmitRequest(OptRequestMessage request) { try { - lg.info("Sending optimization submit request for job {}", request.jobId()); + lg.info("Sending optimization submit request for job {}", request.jobId); optRequestEmitter.send(mapper.writeValueAsString(request)); } catch (Exception e) { - lg.error("Failed to send optimization submit request for job {}: {}", request.jobId(), e.getMessage(), e); + lg.error("Failed to send optimization submit request for job {}: {}", request.jobId, e.getMessage(), e); } } @@ -53,10 +52,10 @@ public void sendSubmitRequest(OptRequestMessage request) { */ public void sendStopRequest(OptRequestMessage request) { try { - lg.info("Sending optimization stop request for job {}", request.jobId()); + lg.info("Sending optimization stop request for job {}", request.jobId); optRequestEmitter.send(mapper.writeValueAsString(request)); } catch (Exception e) { - lg.error("Failed to send optimization stop request for job {}: {}", request.jobId(), e.getMessage(), e); + lg.error("Failed to send optimization stop request for job {}: {}", request.jobId, e.getMessage(), e); } } @@ -67,15 +66,15 @@ public void sendStopRequest(OptRequestMessage request) { public Uni consumeOptStatus(Message message) { try { OptStatusMessage statusMsg = mapper.readValue(message.getPayload(), OptStatusMessage.class); - lg.info("Received optimization status update: job={}, status={}", statusMsg.jobId(), statusMsg.status()); + lg.info("Received optimization status update: job={}, status={}", statusMsg.jobId, statusMsg.status); - KeyValue jobKey = new KeyValue(statusMsg.jobId()); + KeyValue jobKey = new KeyValue(statusMsg.jobId); - if (statusMsg.htcJobId() != null) { - optimizationRestService.updateHtcJobId(jobKey, statusMsg.htcJobId()); + if (statusMsg.htcJobId != null) { + optimizationRestService.updateHtcJobId(jobKey, statusMsg.htcJobId); } - if (statusMsg.status() != null) { - optimizationRestService.updateOptJobStatus(jobKey, statusMsg.status(), statusMsg.statusMessage()); + if (statusMsg.status != null) { + optimizationRestService.updateOptJobStatus(jobKey, statusMsg.status, statusMsg.statusMessage); } return Uni.createFrom().completionStage(message.ack()); @@ -84,26 +83,4 @@ public Uni consumeOptStatus(Message message) { return Uni.createFrom().completionStage(message.nack(e)); } } - - /** - * Message sent from vcell-rest to vcell-submit to request job submission or stop. - */ - public record OptRequestMessage( - String jobId, - String command, // "submit" or "stop" - String optProblemFilePath, // for submit - String optOutputFilePath, // for submit - String optReportFilePath, // for submit - String htcJobId // for stop (SLURM job to cancel) - ) {} - - /** - * Message sent from vcell-submit back to vcell-rest with status updates. - */ - public record OptStatusMessage( - String jobId, - OptJobStatus status, - String statusMessage, - String htcJobId // set when SLURM job is submitted - ) {} } diff --git a/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java b/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java index 971548a8c2..ffdb27df21 100644 --- a/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java +++ b/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java @@ -6,13 +6,14 @@ import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import org.eclipse.microprofile.openapi.annotations.Operation; +import org.vcell.optimization.OptRequestMessage; import org.vcell.optimization.jtd.OptProblem; import org.vcell.restq.activemq.OptimizationMQ; import org.vcell.restq.errors.exceptions.DataAccessWebException; import org.vcell.restq.errors.exceptions.NotAuthenticatedWebException; import org.vcell.restq.errors.exceptions.NotFoundWebException; import org.vcell.restq.errors.exceptions.RuntimeWebException; -import org.vcell.restq.models.OptJobStatus; +import org.vcell.optimization.OptJobStatus; import org.vcell.restq.models.OptimizationJobStatus; import org.vcell.restq.services.OptimizationRestService; import org.vcell.restq.services.UserRestService; @@ -70,7 +71,7 @@ public OptimizationJobStatus submit(OptProblem optProblem) OptimizationJobStatus status = optimizationRestService.submitOptimization( optProblem, new File(PAREST_DATA_DIR), vcellUser); - optimizationMQ.sendSubmitRequest(new OptimizationMQ.OptRequestMessage( + optimizationMQ.sendSubmitRequest(new OptRequestMessage( status.id().toString(), "submit", new File(PAREST_DATA_DIR, "CopasiParest_" + status.id() + "_optProblem.json").getAbsolutePath(), @@ -120,7 +121,7 @@ public OptimizationJobStatus stop(@PathParam("optId") Long optId) } String htcJobId = optimizationRestService.getHtcJobId(jobKey); - optimizationMQ.sendStopRequest(new OptimizationMQ.OptRequestMessage( + optimizationMQ.sendStopRequest(new OptRequestMessage( jobKey.toString(), "stop", null, null, null, diff --git a/vcell-rest/src/main/java/org/vcell/restq/models/OptimizationJobStatus.java b/vcell-rest/src/main/java/org/vcell/restq/models/OptimizationJobStatus.java index 8bc969df4c..943d107b05 100644 --- a/vcell-rest/src/main/java/org/vcell/restq/models/OptimizationJobStatus.java +++ b/vcell-rest/src/main/java/org/vcell/restq/models/OptimizationJobStatus.java @@ -1,5 +1,6 @@ package org.vcell.restq.models; +import org.vcell.optimization.OptJobStatus; import org.vcell.optimization.jtd.OptProgressReport; import org.vcell.optimization.jtd.Vcellopt; import org.vcell.util.document.KeyValue; diff --git a/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java b/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java index 035c454331..0fa3b26b41 100644 --- a/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java +++ b/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java @@ -10,7 +10,7 @@ import org.vcell.optimization.jtd.OptProgressReport; import org.vcell.optimization.jtd.Vcellopt; import org.vcell.restq.db.AgroalConnectionFactory; -import org.vcell.restq.models.OptJobStatus; +import org.vcell.optimization.OptJobStatus; import org.vcell.restq.models.OptimizationJobStatus; import org.vcell.util.DataAccessException; import org.vcell.util.document.KeyValue; diff --git a/vcell-server/src/main/java/cbit/vcell/message/server/batch/opt/OptimizationBatchServer.java b/vcell-server/src/main/java/cbit/vcell/message/server/batch/opt/OptimizationBatchServer.java index 83cdbfc928..76007aa580 100644 --- a/vcell-server/src/main/java/cbit/vcell/message/server/batch/opt/OptimizationBatchServer.java +++ b/vcell-server/src/main/java/cbit/vcell/message/server/batch/opt/OptimizationBatchServer.java @@ -8,7 +8,10 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.vcell.optimization.CopasiUtils; +import org.vcell.optimization.OptJobStatus; import org.vcell.optimization.OptMessage; +import org.vcell.optimization.OptRequestMessage; +import org.vcell.optimization.OptStatusMessage; import org.vcell.optimization.jtd.OptProblem; import org.vcell.optimization.jtd.OptProgressReport; import org.vcell.optimization.jtd.Vcellopt; @@ -296,12 +299,12 @@ private void handleSubmitRequest(OptRequestMessage request, Session session, // Send QUEUED status back with htcJobId sendStatusMessage(session, producer, objectMapper, - request.jobId, "QUEUED", null, htcJobID.toDatabase()); + request.jobId, OptJobStatus.QUEUED, null, htcJobID.toDatabase()); } catch (Exception e) { lg.error("Failed to submit optimization job {}: {}", request.jobId, e.getMessage(), e); try { sendStatusMessage(session, producer, objectMapper, - request.jobId, "FAILED", e.getMessage(), null); + request.jobId, OptJobStatus.FAILED, e.getMessage(), null); } catch (JMSException jmsEx) { lg.error("Failed to send FAILED status for job {}: {}", request.jobId, jmsEx.getMessage(), jmsEx); } @@ -332,7 +335,7 @@ private void handleStopRequest(OptRequestMessage request) { } private void sendStatusMessage(Session session, MessageProducer producer, ObjectMapper objectMapper, - String jobId, String status, String statusMessage, String htcJobId) + String jobId, OptJobStatus status, String statusMessage, String htcJobId) throws JMSException { try { OptStatusMessage statusMsg = new OptStatusMessage(jobId, status, statusMessage, htcJobId); @@ -346,39 +349,6 @@ private void sendStatusMessage(Session session, MessageProducer producer, Object } } - /** - * Message from vcell-rest requesting job submission or stop. - * Must match the JSON format produced by OptimizationMQ.OptRequestMessage in vcell-rest. - */ - public static class OptRequestMessage { - public String jobId; - public String command; - public String optProblemFilePath; - public String optOutputFilePath; - public String optReportFilePath; - public String htcJobId; - } - - /** - * Status message sent back to vcell-rest. - * Must match the JSON format expected by OptimizationMQ.OptStatusMessage in vcell-rest. - */ - public static class OptStatusMessage { - public String jobId; - public String status; - public String statusMessage; - public String htcJobId; - - public OptStatusMessage() {} - - public OptStatusMessage(String jobId, String status, String statusMessage, String htcJobId) { - this.jobId = jobId; - this.status = status; - this.statusMessage = statusMessage; - this.htcJobId = htcJobId; - } - } - private Vcellopt getOptResults(String optID) throws IOException { File f = generateOptOutputFilePath(optID); if (f.exists()) {// opt job done From 36d431686cd4d9587247b1eb15399daa99e9ffdb Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Wed, 8 Apr 2026 12:34:42 -0400 Subject: [PATCH 09/40] Add Quarkus integration tests for optimization REST endpoints Tests cover: submit and verify status, list jobs, progress report from mock report file, auto-transition to COMPLETE on output file, stop running job, unauthorized access (different user), and unauthenticated access (401). Uses testcontainers for PostgreSQL and Keycloak (same infrastructure as existing Quarkus tests). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../vcell/restq/OptimizationResourceTest.java | 358 ++++++++++++++++++ 1 file changed, 358 insertions(+) create mode 100644 vcell-rest/src/test/java/org/vcell/restq/OptimizationResourceTest.java diff --git a/vcell-rest/src/test/java/org/vcell/restq/OptimizationResourceTest.java b/vcell-rest/src/test/java/org/vcell/restq/OptimizationResourceTest.java new file mode 100644 index 0000000000..50038cd0b7 --- /dev/null +++ b/vcell-rest/src/test/java/org/vcell/restq/OptimizationResourceTest.java @@ -0,0 +1,358 @@ +package org.vcell.restq; + +import cbit.vcell.resource.PropertyLoader; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.keycloak.client.KeycloakTestClient; +import jakarta.inject.Inject; +import org.junit.jupiter.api.*; +import org.vcell.optimization.CopasiUtils; +import org.vcell.optimization.OptJobStatus; +import org.vcell.optimization.jtd.*; +import org.vcell.restclient.ApiClient; +import org.vcell.restclient.ApiException; +import org.vcell.restclient.api.UsersResourceApi; +import org.vcell.restq.config.CDIVCellConfigProvider; +import org.vcell.restq.db.AgroalConnectionFactory; +import org.vcell.restq.models.OptimizationJobStatus; +import org.vcell.restq.services.OptimizationRestService; +import org.vcell.util.DataAccessException; +import org.vcell.util.document.KeyValue; +import org.vcell.util.document.User; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; + +import static io.restassured.RestAssured.given; +import static org.junit.jupiter.api.Assertions.*; + +@QuarkusTest +@Tag("Quarkus") +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class OptimizationResourceTest { + + @Inject + ObjectMapper objectMapper; + + @Inject + AgroalConnectionFactory agroalConnectionFactory; + + @Inject + OptimizationRestService optimizationRestService; + + @Inject + @org.eclipse.microprofile.config.inject.ConfigProperty(name = "quarkus.http.test-port") + Integer testPort; + + KeycloakTestClient keycloakClient = new KeycloakTestClient(); + + private ApiClient aliceAPIClient; + + @BeforeAll + public static void setupConfig() { + PropertyLoader.setConfigProvider(new CDIVCellConfigProvider()); + } + + @BeforeEach + public void createClients() { + aliceAPIClient = TestEndpointUtils.createAuthenticatedAPIClient(keycloakClient, testPort, TestEndpointUtils.TestOIDCUsers.alice); + } + + @AfterEach + public void removeOIDCMappings() throws SQLException, DataAccessException { + TestEndpointUtils.removeAllMappings(agroalConnectionFactory); + } + + @Test + @Order(1) + public void testSubmitAndGetStatus() throws Exception { + // Map alice to admin user + TestEndpointUtils.mapApiClientToAdmin(aliceAPIClient); + String accessToken = keycloakClient.getAccessToken("alice"); + + // Create a minimal OptProblem + OptProblem optProblem = createTestOptProblem(); + + // Submit via REST + String responseJson = given() + .header("Authorization", "Bearer " + accessToken) + .contentType("application/json") + .body(objectMapper.writeValueAsString(optProblem)) + .when() + .post("/api/v1/optimization") + .then() + .statusCode(200) + .extract().body().asString(); + + OptimizationJobStatus submitStatus = objectMapper.readValue(responseJson, OptimizationJobStatus.class); + assertNotNull(submitStatus.id()); + assertEquals(OptJobStatus.SUBMITTED, submitStatus.status()); + assertNull(submitStatus.progressReport()); + assertNull(submitStatus.results()); + + // Query status + String statusJson = given() + .header("Authorization", "Bearer " + accessToken) + .when() + .get("/api/v1/optimization/" + submitStatus.id()) + .then() + .statusCode(200) + .extract().body().asString(); + + OptimizationJobStatus getStatus = objectMapper.readValue(statusJson, OptimizationJobStatus.class); + assertEquals(submitStatus.id(), getStatus.id()); + assertEquals(OptJobStatus.SUBMITTED, getStatus.status()); + } + + @Test + @Order(2) + public void testListJobs() throws Exception { + TestEndpointUtils.mapApiClientToAdmin(aliceAPIClient); + String accessToken = keycloakClient.getAccessToken("alice"); + User adminUser = TestEndpointUtils.administratorUser; + + File tempDir = Files.createTempDirectory("parest_test").toFile(); + try { + // Submit two jobs directly via service + OptProblem optProblem = createTestOptProblem(); + OptimizationJobStatus job1 = optimizationRestService.submitOptimization(optProblem, tempDir, adminUser); + OptimizationJobStatus job2 = optimizationRestService.submitOptimization(optProblem, tempDir, adminUser); + + // List via REST + String listJson = given() + .header("Authorization", "Bearer " + accessToken) + .when() + .get("/api/v1/optimization") + .then() + .statusCode(200) + .extract().body().asString(); + + OptimizationJobStatus[] jobs = objectMapper.readValue(listJson, OptimizationJobStatus[].class); + assertTrue(jobs.length >= 2, "Expected at least 2 jobs, got " + jobs.length); + + // Most recent first + for (OptimizationJobStatus job : jobs) { + assertNull(job.progressReport(), "List should not include progressReport"); + assertNull(job.results(), "List should not include results"); + } + } finally { + deleteRecursive(tempDir); + } + } + + @Test + @Order(3) + public void testStatusWithProgressReport() throws Exception { + TestEndpointUtils.mapApiClientToAdmin(aliceAPIClient); + String accessToken = keycloakClient.getAccessToken("alice"); + User adminUser = TestEndpointUtils.administratorUser; + + File tempDir = Files.createTempDirectory("parest_test").toFile(); + try { + OptProblem optProblem = createTestOptProblem(); + OptimizationJobStatus job = optimizationRestService.submitOptimization(optProblem, tempDir, adminUser); + + // Simulate vcell-submit reporting RUNNING + optimizationRestService.updateHtcJobId(job.id(), "SLURM:12345"); + optimizationRestService.updateOptJobStatus(job.id(), OptJobStatus.RUNNING, null); + + // Write a mock progress report file + File reportFile = new File(tempDir, "CopasiParest_" + job.id() + "_optReport.txt"); + // Header: JSON array of param names, then TSV rows: evaluations \t objective \t param values + Files.writeString(reportFile.toPath(), + "[\"k1\",\"k2\"]\n" + + "10\t0.5\t1.0\t2.0\n" + + "20\t0.1\t1.1\t2.1\n"); + + // Query status + String statusJson = given() + .header("Authorization", "Bearer " + accessToken) + .when() + .get("/api/v1/optimization/" + job.id()) + .then() + .statusCode(200) + .extract().body().asString(); + + OptimizationJobStatus status = objectMapper.readValue(statusJson, OptimizationJobStatus.class); + assertEquals(OptJobStatus.RUNNING, status.status()); + assertEquals("SLURM:12345", status.htcJobId()); + assertNotNull(status.progressReport(), "RUNNING status should include progress report"); + assertNull(status.results(), "RUNNING status should not include results"); + } finally { + deleteRecursive(tempDir); + } + } + + @Test + @Order(4) + public void testStatusWithResults() throws Exception { + TestEndpointUtils.mapApiClientToAdmin(aliceAPIClient); + String accessToken = keycloakClient.getAccessToken("alice"); + User adminUser = TestEndpointUtils.administratorUser; + + File tempDir = Files.createTempDirectory("parest_test").toFile(); + try { + OptProblem optProblem = createTestOptProblem(); + OptimizationJobStatus job = optimizationRestService.submitOptimization(optProblem, tempDir, adminUser); + optimizationRestService.updateOptJobStatus(job.id(), OptJobStatus.RUNNING, null); + + // Write a mock output file (simulates solver completion) + File outputFile = new File(tempDir, "CopasiParest_" + job.id() + "_optRun.json"); + Vcellopt vcellopt = new Vcellopt(); + vcellopt.setOptProblem(optProblem); + vcellopt.setStatus(VcelloptStatus.COMPLETE); + vcellopt.setStatusMessage("optimization complete"); + OptResultSet resultSet = new OptResultSet(); + resultSet.setNumFunctionEvaluations(50); + resultSet.setObjectiveFunction(0.001); + resultSet.setOptParameterValues(Map.of("k1", 1.5, "k2", 2.5)); + vcellopt.setOptResultSet(resultSet); + objectMapper.writeValue(outputFile, vcellopt); + + // Query status — should auto-transition to COMPLETE + String statusJson = given() + .header("Authorization", "Bearer " + accessToken) + .when() + .get("/api/v1/optimization/" + job.id()) + .then() + .statusCode(200) + .extract().body().asString(); + + OptimizationJobStatus status = objectMapper.readValue(statusJson, OptimizationJobStatus.class); + assertEquals(OptJobStatus.COMPLETE, status.status()); + assertNotNull(status.results(), "COMPLETE status should include results"); + assertEquals(VcelloptStatus.COMPLETE, status.results().getStatus()); + } finally { + deleteRecursive(tempDir); + } + } + + @Test + @Order(5) + public void testStopJob() throws Exception { + TestEndpointUtils.mapApiClientToAdmin(aliceAPIClient); + String accessToken = keycloakClient.getAccessToken("alice"); + User adminUser = TestEndpointUtils.administratorUser; + + File tempDir = Files.createTempDirectory("parest_test").toFile(); + try { + OptProblem optProblem = createTestOptProblem(); + OptimizationJobStatus job = optimizationRestService.submitOptimization(optProblem, tempDir, adminUser); + optimizationRestService.updateHtcJobId(job.id(), "SLURM:99999"); + optimizationRestService.updateOptJobStatus(job.id(), OptJobStatus.RUNNING, null); + + // Stop via REST + String stopJson = given() + .header("Authorization", "Bearer " + accessToken) + .contentType("application/json") + .when() + .post("/api/v1/optimization/" + job.id() + "/stop") + .then() + .statusCode(200) + .extract().body().asString(); + + OptimizationJobStatus stopStatus = objectMapper.readValue(stopJson, OptimizationJobStatus.class); + assertEquals(OptJobStatus.STOPPED, stopStatus.status()); + assertEquals("Stopped by user", stopStatus.statusMessage()); + } finally { + deleteRecursive(tempDir); + } + } + + @Test + @Order(6) + public void testUnauthorizedAccess() throws Exception { + // Submit as alice/admin + TestEndpointUtils.mapApiClientToAdmin(aliceAPIClient); + User adminUser = TestEndpointUtils.administratorUser; + + File tempDir = Files.createTempDirectory("parest_test").toFile(); + try { + OptProblem optProblem = createTestOptProblem(); + OptimizationJobStatus job = optimizationRestService.submitOptimization(optProblem, tempDir, adminUser); + + // Try to access as bob (different user) + ApiClient bobAPIClient = TestEndpointUtils.createAuthenticatedAPIClient(keycloakClient, testPort, TestEndpointUtils.TestOIDCUsers.bob); + TestEndpointUtils.mapApiClientToNagios(bobAPIClient); + String bobToken = keycloakClient.getAccessToken("bob"); + + given() + .header("Authorization", "Bearer " + bobToken) + .when() + .get("/api/v1/optimization/" + job.id()) + .then() + .statusCode(500); // DataAccessException mapped to 500 + } finally { + deleteRecursive(tempDir); + } + } + + @Test + @Order(7) + public void testUnauthenticatedAccess() { + // No auth token — should get 401 + given() + .when() + .get("/api/v1/optimization") + .then() + .statusCode(401); + } + + private OptProblem createTestOptProblem() { + OptProblem optProblem = new OptProblem(); + optProblem.setMathModelSbmlContents("test"); + optProblem.setNumberOfOptimizationRuns(1); + + ParameterDescription p1 = new ParameterDescription(); + p1.setName("k1"); + p1.setMinValue(0.0); + p1.setMaxValue(10.0); + p1.setInitialValue(1.0); + p1.setScale(1.0); + + ParameterDescription p2 = new ParameterDescription(); + p2.setName("k2"); + p2.setMinValue(0.0); + p2.setMaxValue(10.0); + p2.setInitialValue(2.0); + p2.setScale(1.0); + + optProblem.setParameterDescriptionList(List.of(p1, p2)); + optProblem.setDataSet(List.of( + List.of(0.0, 1.0, 2.0), + List.of(1.0, 1.5, 2.5) + )); + + ReferenceVariable rv1 = new ReferenceVariable(); + rv1.setVarName("t"); + rv1.setReferenceVariableType(ReferenceVariableReferenceVariableType.INDEPENDENT); + ReferenceVariable rv2 = new ReferenceVariable(); + rv2.setVarName("x"); + rv2.setReferenceVariableType(ReferenceVariableReferenceVariableType.DEPENDENT); + + optProblem.setReferenceVariable(List.of(rv1, rv2)); + + CopasiOptimizationMethod method = new CopasiOptimizationMethod(); + method.setOptimizationMethodType(CopasiOptimizationMethodOptimizationMethodType.EVOLUTIONARY_PROGRAM); + method.setOptimizationParameter(List.of()); + optProblem.setCopasiOptimizationMethod(method); + + return optProblem; + } + + private static void deleteRecursive(File file) { + if (file.isDirectory()) { + File[] children = file.listFiles(); + if (children != null) { + for (File child : children) { + deleteRecursive(child); + } + } + } + file.delete(); + } +} From 90c24b49e0d5a876201759d5b2a7c6dff5647f82 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Wed, 8 Apr 2026 13:24:08 -0400 Subject: [PATCH 10/40] Consolidate OpenAPI scripts and regenerate clients with optimization endpoints - Add tools/openapi-clients.sh: single script for spec generation and client generation, with --update-spec flag to optionally rebuild vcell-rest first. Replaces tools/generate.sh and tools/compile-and-build-clients.sh. - Regenerate OpenAPI spec with new optimization endpoints: GET/POST /api/v1/optimization, GET /api/v1/optimization/{optId}, POST /api/v1/optimization/{optId}/stop - Regenerate Java (vcell-restclient), Python (python-restclient), and TypeScript-Angular (webapp-ng) clients - Update CLAUDE.md with new script name and usage Co-Authored-By: Claude Opus 4.6 (1M context) --- CLAUDE.md | 13 +- python-restclient/.openapi-generator/FILES | 51 + python-restclient/README.md | 20 + .../docs/CopasiOptimizationMethod.md | 29 + ...ptimizationMethodOptimizationMethodType.md | 10 + .../docs/CopasiOptimizationParameter.md | 30 + .../CopasiOptimizationParameterDataType.md | 10 + .../CopasiOptimizationParameterParamType.md | 10 + python-restclient/docs/OptJobStatus.md | 10 + python-restclient/docs/OptProblem.md | 33 + python-restclient/docs/OptProgressItem.md | 29 + python-restclient/docs/OptProgressReport.md | 29 + python-restclient/docs/OptResultSet.md | 31 + .../docs/OptimizationJobStatus.md | 33 + .../docs/OptimizationResourceApi.md | 299 +++++ .../docs/ParameterDescription.md | 32 + python-restclient/docs/ReferenceVariable.md | 29 + .../ReferenceVariableReferenceVariableType.md | 10 + python-restclient/docs/Vcellopt.md | 31 + python-restclient/docs/VcelloptStatus.md | 10 + .../test/test_copasi_optimization_method.py | 59 + ...ization_method_optimization_method_type.py | 35 + .../test_copasi_optimization_parameter.py | 55 + ...copasi_optimization_parameter_data_type.py | 35 + ...opasi_optimization_parameter_param_type.py | 35 + python-restclient/test/test_opt_job_status.py | 35 + python-restclient/test/test_opt_problem.py | 80 ++ .../test/test_opt_progress_item.py | 54 + .../test/test_opt_progress_report.py | 60 + python-restclient/test/test_opt_result_set.py | 66 + .../test/test_optimization_job_status.py | 112 ++ .../test/test_optimization_resource_api.py | 60 + .../test/test_parameter_description.py | 57 + .../test/test_reference_variable.py | 54 + ...erence_variable_reference_variable_type.py | 35 + python-restclient/test/test_vcellopt.py | 98 ++ .../test/test_vcellopt_status.py | 35 + python-restclient/vcell_client/__init__.py | 17 + .../vcell_client/api/__init__.py | 1 + .../api/optimization_resource_api.py | 1125 +++++++++++++++++ .../vcell_client/models/__init__.py | 16 + .../models/copasi_optimization_method.py | 105 ++ ...ization_method_optimization_method_type.py | 57 + .../models/copasi_optimization_parameter.py | 100 ++ ...copasi_optimization_parameter_data_type.py | 46 + ...opasi_optimization_parameter_param_type.py | 58 + .../vcell_client/models/opt_job_status.py | 50 + .../vcell_client/models/opt_problem.py | 124 ++ .../vcell_client/models/opt_progress_item.py | 96 ++ .../models/opt_progress_report.py | 104 ++ .../vcell_client/models/opt_result_set.py | 104 ++ .../models/optimization_job_status.py | 113 ++ .../models/parameter_description.py | 102 ++ .../vcell_client/models/reference_variable.py | 97 ++ ...erence_variable_reference_variable_type.py | 46 + .../vcell_client/models/vcellopt.py | 109 ++ .../vcell_client/models/vcellopt_status.py | 48 + tools/openapi-clients.sh | 105 ++ tools/openapi.yaml | 342 +++++ vcell-restclient/.openapi-generator/FILES | 51 + vcell-restclient/README.md | 24 + vcell-restclient/api/openapi.yaml | 541 ++++++++ .../docs/CopasiOptimizationMethod.md | 14 + ...ptimizationMethodOptimizationMethodType.md | 35 + .../docs/CopasiOptimizationParameter.md | 15 + .../CopasiOptimizationParameterDataType.md | 13 + .../CopasiOptimizationParameterParamType.md | 37 + vcell-restclient/docs/OptJobStatus.md | 21 + vcell-restclient/docs/OptProblem.md | 18 + vcell-restclient/docs/OptProgressItem.md | 14 + vcell-restclient/docs/OptProgressReport.md | 14 + vcell-restclient/docs/OptResultSet.md | 16 + .../docs/OptimizationJobStatus.md | 18 + .../docs/OptimizationResourceApi.md | 572 +++++++++ vcell-restclient/docs/ParameterDescription.md | 17 + vcell-restclient/docs/ReferenceVariable.md | 14 + .../ReferenceVariableReferenceVariableType.md | 13 + vcell-restclient/docs/Vcellopt.md | 16 + vcell-restclient/docs/VcelloptStatus.md | 17 + .../api/OptimizationResourceApi.java | 374 ++++++ .../model/CopasiOptimizationMethod.java | 203 +++ ...imizationMethodOptimizationMethodType.java | 100 ++ .../model/CopasiOptimizationParameter.java | 224 ++++ .../CopasiOptimizationParameterDataType.java | 78 ++ .../CopasiOptimizationParameterParamType.java | 102 ++ .../vcell/restclient/model/OptJobStatus.java | 86 ++ .../vcell/restclient/model/OptProblem.java | 373 ++++++ .../restclient/model/OptProgressItem.java | 186 +++ .../restclient/model/OptProgressReport.java | 216 ++++ .../vcell/restclient/model/OptResultSet.java | 273 ++++ .../model/OptimizationJobStatus.java | 333 +++++ .../model/ParameterDescription.java | 294 +++++ .../restclient/model/ReferenceVariable.java | 187 +++ ...eferenceVariableReferenceVariableType.java | 78 ++ .../org/vcell/restclient/model/Vcellopt.java | 261 ++++ .../restclient/model/VcelloptStatus.java | 82 ++ .../api/OptimizationResourceApiTest.java | 106 ++ ...ationMethodOptimizationMethodTypeTest.java | 32 + .../model/CopasiOptimizationMethodTest.java | 60 + ...pasiOptimizationParameterDataTypeTest.java | 32 + ...asiOptimizationParameterParamTypeTest.java | 32 + .../CopasiOptimizationParameterTest.java | 66 + .../restclient/model/OptJobStatusTest.java | 32 + .../restclient/model/OptProblemTest.java | 93 ++ .../restclient/model/OptProgressItemTest.java | 56 + .../model/OptProgressReportTest.java | 61 + .../restclient/model/OptResultSetTest.java | 75 ++ .../model/OptimizationJobStatusTest.java | 91 ++ .../model/ParameterDescriptionTest.java | 80 ++ ...enceVariableReferenceVariableTypeTest.java | 32 + .../model/ReferenceVariableTest.java | 57 + .../restclient/model/VcelloptStatusTest.java | 32 + .../vcell/restclient/model/VcelloptTest.java | 75 ++ .../modules/openapi/.openapi-generator/FILES | 18 + .../src/app/core/modules/openapi/api/api.ts | 5 +- .../api/optimization-resource.service.ts | 360 ++++++ .../optimization-resource.serviceInterface.ts | 56 + ...ization-method-optimization-method-type.ts | 31 + .../model/copasi-optimization-method.ts | 23 + ...copasi-optimization-parameter-data-type.ts | 20 + ...opasi-optimization-parameter-param-type.ts | 32 + .../model/copasi-optimization-parameter.ts | 24 + .../app/core/modules/openapi/model/models.ts | 16 + .../modules/openapi/model/opt-job-status.ts | 24 + .../core/modules/openapi/model/opt-problem.ts | 25 + .../openapi/model/opt-progress-item.ts | 18 + .../openapi/model/opt-progress-report.ts | 19 + .../modules/openapi/model/opt-result-set.ts | 21 + .../openapi/model/optimization-job-status.ts | 28 + .../openapi/model/parameter-description.ts | 21 + ...erence-variable-reference-variable-type.ts | 20 + .../openapi/model/reference-variable.ts | 22 + .../modules/openapi/model/vcellopt-status.ts | 22 + .../core/modules/openapi/model/vcellopt.ts | 26 + 134 files changed, 11446 insertions(+), 6 deletions(-) create mode 100644 python-restclient/docs/CopasiOptimizationMethod.md create mode 100644 python-restclient/docs/CopasiOptimizationMethodOptimizationMethodType.md create mode 100644 python-restclient/docs/CopasiOptimizationParameter.md create mode 100644 python-restclient/docs/CopasiOptimizationParameterDataType.md create mode 100644 python-restclient/docs/CopasiOptimizationParameterParamType.md create mode 100644 python-restclient/docs/OptJobStatus.md create mode 100644 python-restclient/docs/OptProblem.md create mode 100644 python-restclient/docs/OptProgressItem.md create mode 100644 python-restclient/docs/OptProgressReport.md create mode 100644 python-restclient/docs/OptResultSet.md create mode 100644 python-restclient/docs/OptimizationJobStatus.md create mode 100644 python-restclient/docs/OptimizationResourceApi.md create mode 100644 python-restclient/docs/ParameterDescription.md create mode 100644 python-restclient/docs/ReferenceVariable.md create mode 100644 python-restclient/docs/ReferenceVariableReferenceVariableType.md create mode 100644 python-restclient/docs/Vcellopt.md create mode 100644 python-restclient/docs/VcelloptStatus.md create mode 100644 python-restclient/test/test_copasi_optimization_method.py create mode 100644 python-restclient/test/test_copasi_optimization_method_optimization_method_type.py create mode 100644 python-restclient/test/test_copasi_optimization_parameter.py create mode 100644 python-restclient/test/test_copasi_optimization_parameter_data_type.py create mode 100644 python-restclient/test/test_copasi_optimization_parameter_param_type.py create mode 100644 python-restclient/test/test_opt_job_status.py create mode 100644 python-restclient/test/test_opt_problem.py create mode 100644 python-restclient/test/test_opt_progress_item.py create mode 100644 python-restclient/test/test_opt_progress_report.py create mode 100644 python-restclient/test/test_opt_result_set.py create mode 100644 python-restclient/test/test_optimization_job_status.py create mode 100644 python-restclient/test/test_optimization_resource_api.py create mode 100644 python-restclient/test/test_parameter_description.py create mode 100644 python-restclient/test/test_reference_variable.py create mode 100644 python-restclient/test/test_reference_variable_reference_variable_type.py create mode 100644 python-restclient/test/test_vcellopt.py create mode 100644 python-restclient/test/test_vcellopt_status.py create mode 100644 python-restclient/vcell_client/api/optimization_resource_api.py create mode 100644 python-restclient/vcell_client/models/copasi_optimization_method.py create mode 100644 python-restclient/vcell_client/models/copasi_optimization_method_optimization_method_type.py create mode 100644 python-restclient/vcell_client/models/copasi_optimization_parameter.py create mode 100644 python-restclient/vcell_client/models/copasi_optimization_parameter_data_type.py create mode 100644 python-restclient/vcell_client/models/copasi_optimization_parameter_param_type.py create mode 100644 python-restclient/vcell_client/models/opt_job_status.py create mode 100644 python-restclient/vcell_client/models/opt_problem.py create mode 100644 python-restclient/vcell_client/models/opt_progress_item.py create mode 100644 python-restclient/vcell_client/models/opt_progress_report.py create mode 100644 python-restclient/vcell_client/models/opt_result_set.py create mode 100644 python-restclient/vcell_client/models/optimization_job_status.py create mode 100644 python-restclient/vcell_client/models/parameter_description.py create mode 100644 python-restclient/vcell_client/models/reference_variable.py create mode 100644 python-restclient/vcell_client/models/reference_variable_reference_variable_type.py create mode 100644 python-restclient/vcell_client/models/vcellopt.py create mode 100644 python-restclient/vcell_client/models/vcellopt_status.py create mode 100755 tools/openapi-clients.sh create mode 100644 vcell-restclient/docs/CopasiOptimizationMethod.md create mode 100644 vcell-restclient/docs/CopasiOptimizationMethodOptimizationMethodType.md create mode 100644 vcell-restclient/docs/CopasiOptimizationParameter.md create mode 100644 vcell-restclient/docs/CopasiOptimizationParameterDataType.md create mode 100644 vcell-restclient/docs/CopasiOptimizationParameterParamType.md create mode 100644 vcell-restclient/docs/OptJobStatus.md create mode 100644 vcell-restclient/docs/OptProblem.md create mode 100644 vcell-restclient/docs/OptProgressItem.md create mode 100644 vcell-restclient/docs/OptProgressReport.md create mode 100644 vcell-restclient/docs/OptResultSet.md create mode 100644 vcell-restclient/docs/OptimizationJobStatus.md create mode 100644 vcell-restclient/docs/OptimizationResourceApi.md create mode 100644 vcell-restclient/docs/ParameterDescription.md create mode 100644 vcell-restclient/docs/ReferenceVariable.md create mode 100644 vcell-restclient/docs/ReferenceVariableReferenceVariableType.md create mode 100644 vcell-restclient/docs/Vcellopt.md create mode 100644 vcell-restclient/docs/VcelloptStatus.md create mode 100644 vcell-restclient/src/main/java/org/vcell/restclient/api/OptimizationResourceApi.java create mode 100644 vcell-restclient/src/main/java/org/vcell/restclient/model/CopasiOptimizationMethod.java create mode 100644 vcell-restclient/src/main/java/org/vcell/restclient/model/CopasiOptimizationMethodOptimizationMethodType.java create mode 100644 vcell-restclient/src/main/java/org/vcell/restclient/model/CopasiOptimizationParameter.java create mode 100644 vcell-restclient/src/main/java/org/vcell/restclient/model/CopasiOptimizationParameterDataType.java create mode 100644 vcell-restclient/src/main/java/org/vcell/restclient/model/CopasiOptimizationParameterParamType.java create mode 100644 vcell-restclient/src/main/java/org/vcell/restclient/model/OptJobStatus.java create mode 100644 vcell-restclient/src/main/java/org/vcell/restclient/model/OptProblem.java create mode 100644 vcell-restclient/src/main/java/org/vcell/restclient/model/OptProgressItem.java create mode 100644 vcell-restclient/src/main/java/org/vcell/restclient/model/OptProgressReport.java create mode 100644 vcell-restclient/src/main/java/org/vcell/restclient/model/OptResultSet.java create mode 100644 vcell-restclient/src/main/java/org/vcell/restclient/model/OptimizationJobStatus.java create mode 100644 vcell-restclient/src/main/java/org/vcell/restclient/model/ParameterDescription.java create mode 100644 vcell-restclient/src/main/java/org/vcell/restclient/model/ReferenceVariable.java create mode 100644 vcell-restclient/src/main/java/org/vcell/restclient/model/ReferenceVariableReferenceVariableType.java create mode 100644 vcell-restclient/src/main/java/org/vcell/restclient/model/Vcellopt.java create mode 100644 vcell-restclient/src/main/java/org/vcell/restclient/model/VcelloptStatus.java create mode 100644 vcell-restclient/src/test/java/org/vcell/restclient/api/OptimizationResourceApiTest.java create mode 100644 vcell-restclient/src/test/java/org/vcell/restclient/model/CopasiOptimizationMethodOptimizationMethodTypeTest.java create mode 100644 vcell-restclient/src/test/java/org/vcell/restclient/model/CopasiOptimizationMethodTest.java create mode 100644 vcell-restclient/src/test/java/org/vcell/restclient/model/CopasiOptimizationParameterDataTypeTest.java create mode 100644 vcell-restclient/src/test/java/org/vcell/restclient/model/CopasiOptimizationParameterParamTypeTest.java create mode 100644 vcell-restclient/src/test/java/org/vcell/restclient/model/CopasiOptimizationParameterTest.java create mode 100644 vcell-restclient/src/test/java/org/vcell/restclient/model/OptJobStatusTest.java create mode 100644 vcell-restclient/src/test/java/org/vcell/restclient/model/OptProblemTest.java create mode 100644 vcell-restclient/src/test/java/org/vcell/restclient/model/OptProgressItemTest.java create mode 100644 vcell-restclient/src/test/java/org/vcell/restclient/model/OptProgressReportTest.java create mode 100644 vcell-restclient/src/test/java/org/vcell/restclient/model/OptResultSetTest.java create mode 100644 vcell-restclient/src/test/java/org/vcell/restclient/model/OptimizationJobStatusTest.java create mode 100644 vcell-restclient/src/test/java/org/vcell/restclient/model/ParameterDescriptionTest.java create mode 100644 vcell-restclient/src/test/java/org/vcell/restclient/model/ReferenceVariableReferenceVariableTypeTest.java create mode 100644 vcell-restclient/src/test/java/org/vcell/restclient/model/ReferenceVariableTest.java create mode 100644 vcell-restclient/src/test/java/org/vcell/restclient/model/VcelloptStatusTest.java create mode 100644 vcell-restclient/src/test/java/org/vcell/restclient/model/VcelloptTest.java create mode 100644 webapp-ng/src/app/core/modules/openapi/api/optimization-resource.service.ts create mode 100644 webapp-ng/src/app/core/modules/openapi/api/optimization-resource.serviceInterface.ts create mode 100644 webapp-ng/src/app/core/modules/openapi/model/copasi-optimization-method-optimization-method-type.ts create mode 100644 webapp-ng/src/app/core/modules/openapi/model/copasi-optimization-method.ts create mode 100644 webapp-ng/src/app/core/modules/openapi/model/copasi-optimization-parameter-data-type.ts create mode 100644 webapp-ng/src/app/core/modules/openapi/model/copasi-optimization-parameter-param-type.ts create mode 100644 webapp-ng/src/app/core/modules/openapi/model/copasi-optimization-parameter.ts create mode 100644 webapp-ng/src/app/core/modules/openapi/model/opt-job-status.ts create mode 100644 webapp-ng/src/app/core/modules/openapi/model/opt-problem.ts create mode 100644 webapp-ng/src/app/core/modules/openapi/model/opt-progress-item.ts create mode 100644 webapp-ng/src/app/core/modules/openapi/model/opt-progress-report.ts create mode 100644 webapp-ng/src/app/core/modules/openapi/model/opt-result-set.ts create mode 100644 webapp-ng/src/app/core/modules/openapi/model/optimization-job-status.ts create mode 100644 webapp-ng/src/app/core/modules/openapi/model/parameter-description.ts create mode 100644 webapp-ng/src/app/core/modules/openapi/model/reference-variable-reference-variable-type.ts create mode 100644 webapp-ng/src/app/core/modules/openapi/model/reference-variable.ts create mode 100644 webapp-ng/src/app/core/modules/openapi/model/vcellopt-status.ts create mode 100644 webapp-ng/src/app/core/modules/openapi/model/vcellopt.ts diff --git a/CLAUDE.md b/CLAUDE.md index f1f16ea338..b9292de83b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -46,14 +46,17 @@ mvn compile test-compile -pl vcell-rest -am ## OpenAPI Client Generation -OpenAPI spec is generated by Quarkus SmallRye OpenAPI from `vcell-rest` and stored in `tools/openapi.yaml`. Three clients are auto-generated: +OpenAPI spec is generated by Quarkus SmallRye OpenAPI from `vcell-rest` and stored in `tools/openapi.yaml`. Three clients are auto-generated (Java, Python, TypeScript-Angular) using OpenAPI Generator v7.1.0. ```bash -cd tools -./generate.sh # Generates Java, Python, and TypeScript-Angular clients +# Generate clients from existing tools/openapi.yaml (if spec is already up to date) +./tools/openapi-clients.sh + +# Rebuild vcell-rest, regenerate the OpenAPI spec, then generate all clients +./tools/openapi-clients.sh --update-spec ``` -- **Java client:** `vcell-restclient/` (OpenAPI Generator v7.1.0) +- **Java client:** `vcell-restclient/` - **Python client:** `python-restclient/` - **Angular client:** `webapp-ng/src/app/core/modules/openapi/` @@ -124,7 +127,7 @@ GitHub Actions (`.github/workflows/ci_cd.yml`): After major changes (especially removing/renaming user-facing features): - [ ] Check `vcell-client/UserDocumentation/` for references to changed features. This is an ad-hoc XML format compiled into JavaHelp (in-app help) and HTML published at https://vcell.org/webstart/VCell_Tutorials/VCell_Help/index.html. Update documentation to stay consistent with code. -- [ ] After regenerating OpenAPI clients (`tools/generate.sh`), compile downstream: `mvn compile test-compile -pl vcell-rest -am` +- [ ] After regenerating OpenAPI clients (`tools/openapi-clients.sh`), compile downstream: `mvn compile test-compile -pl vcell-rest -am` ## Conventions diff --git a/python-restclient/.openapi-generator/FILES b/python-restclient/.openapi-generator/FILES index 3f9b5211aa..9faadc1937 100644 --- a/python-restclient/.openapi-generator/FILES +++ b/python-restclient/.openapi-generator/FILES @@ -15,6 +15,11 @@ docs/BiomodelRef.md docs/CompositeCurve.md docs/ControlPointCurve.md docs/Coordinate.md +docs/CopasiOptimizationMethod.md +docs/CopasiOptimizationMethodOptimizationMethodType.md +docs/CopasiOptimizationParameter.md +docs/CopasiOptimizationParameterDataType.md +docs/CopasiOptimizationParameterParamType.md docs/Curve.md docs/CurveSelectionInfo.md docs/DataIdentifier.md @@ -54,11 +59,21 @@ docs/MathType.md docs/MathmodelRef.md docs/ModelType.md docs/N5ExportRequest.md +docs/OptJobStatus.md +docs/OptProblem.md +docs/OptProgressItem.md +docs/OptProgressReport.md +docs/OptResultSet.md +docs/OptimizationJobStatus.md +docs/OptimizationResourceApi.md docs/Origin.md +docs/ParameterDescription.md docs/Publication.md docs/PublicationInfo.md docs/PublicationResourceApi.md docs/PublishModelsRequest.md +docs/ReferenceVariable.md +docs/ReferenceVariableReferenceVariableType.md docs/SPECIALCLAIM.md docs/SampledCurve.md docs/SchedulerStatus.md @@ -97,9 +112,28 @@ docs/VariableDomain.md docs/VariableMode.md docs/VariableSpecs.md docs/VariableType.md +docs/Vcellopt.md +docs/VcelloptStatus.md docs/Version.md docs/VersionFlag.md test/__init__.py +test/test_copasi_optimization_method.py +test/test_copasi_optimization_method_optimization_method_type.py +test/test_copasi_optimization_parameter.py +test/test_copasi_optimization_parameter_data_type.py +test/test_copasi_optimization_parameter_param_type.py +test/test_opt_job_status.py +test/test_opt_problem.py +test/test_opt_progress_item.py +test/test_opt_progress_report.py +test/test_opt_result_set.py +test/test_optimization_job_status.py +test/test_optimization_resource_api.py +test/test_parameter_description.py +test/test_reference_variable.py +test/test_reference_variable_reference_variable_type.py +test/test_vcellopt.py +test/test_vcellopt_status.py tox.ini vcell_client/__init__.py vcell_client/api/__init__.py @@ -110,6 +144,7 @@ vcell_client/api/field_data_resource_api.py vcell_client/api/geometry_resource_api.py vcell_client/api/hello_world_api.py vcell_client/api/math_model_resource_api.py +vcell_client/api/optimization_resource_api.py vcell_client/api/publication_resource_api.py vcell_client/api/simulation_resource_api.py vcell_client/api/solver_resource_api.py @@ -132,6 +167,11 @@ vcell_client/models/biomodel_ref.py vcell_client/models/composite_curve.py vcell_client/models/control_point_curve.py vcell_client/models/coordinate.py +vcell_client/models/copasi_optimization_method.py +vcell_client/models/copasi_optimization_method_optimization_method_type.py +vcell_client/models/copasi_optimization_parameter.py +vcell_client/models/copasi_optimization_parameter_data_type.py +vcell_client/models/copasi_optimization_parameter_param_type.py vcell_client/models/curve.py vcell_client/models/curve_selection_info.py vcell_client/models/data_identifier.py @@ -166,10 +206,19 @@ vcell_client/models/math_type.py vcell_client/models/mathmodel_ref.py vcell_client/models/model_type.py vcell_client/models/n5_export_request.py +vcell_client/models/opt_job_status.py +vcell_client/models/opt_problem.py +vcell_client/models/opt_progress_item.py +vcell_client/models/opt_progress_report.py +vcell_client/models/opt_result_set.py +vcell_client/models/optimization_job_status.py vcell_client/models/origin.py +vcell_client/models/parameter_description.py vcell_client/models/publication.py vcell_client/models/publication_info.py vcell_client/models/publish_models_request.py +vcell_client/models/reference_variable.py +vcell_client/models/reference_variable_reference_variable_type.py vcell_client/models/sampled_curve.py vcell_client/models/scheduler_status.py vcell_client/models/simulation_execution_status_record.py @@ -204,6 +253,8 @@ vcell_client/models/variable_type.py vcell_client/models/vc_document_type.py vcell_client/models/vc_image_summary.py vcell_client/models/vc_simulation_identifier.py +vcell_client/models/vcellopt.py +vcell_client/models/vcellopt_status.py vcell_client/models/version.py vcell_client/models/version_flag.py vcell_client/py.typed diff --git a/python-restclient/README.md b/python-restclient/README.md index a7449b2810..f78801de66 100644 --- a/python-restclient/README.md +++ b/python-restclient/README.md @@ -118,6 +118,10 @@ Class | Method | HTTP request | Description *MathModelResourceApi* | [**get_summary**](docs/MathModelResourceApi.md#get_summary) | **GET** /api/v1/mathModel/summary/{id} | *MathModelResourceApi* | [**get_vcml**](docs/MathModelResourceApi.md#get_vcml) | **GET** /api/v1/mathModel/{id} | *MathModelResourceApi* | [**save_math_model**](docs/MathModelResourceApi.md#save_math_model) | **POST** /api/v1/mathModel | +*OptimizationResourceApi* | [**get_optimization_status**](docs/OptimizationResourceApi.md#get_optimization_status) | **GET** /api/v1/optimization/{optId} | Get status, progress, or results of an optimization job +*OptimizationResourceApi* | [**list_optimization_jobs**](docs/OptimizationResourceApi.md#list_optimization_jobs) | **GET** /api/v1/optimization | List optimization jobs for the authenticated user +*OptimizationResourceApi* | [**stop_optimization**](docs/OptimizationResourceApi.md#stop_optimization) | **POST** /api/v1/optimization/{optId}/stop | Stop a running optimization job +*OptimizationResourceApi* | [**submit_optimization**](docs/OptimizationResourceApi.md#submit_optimization) | **POST** /api/v1/optimization | Submit a new parameter estimation optimization job *PublicationResourceApi* | [**create_publication**](docs/PublicationResourceApi.md#create_publication) | **POST** /api/v1/publications | Create publication *PublicationResourceApi* | [**delete_publication**](docs/PublicationResourceApi.md#delete_publication) | **DELETE** /api/v1/publications/{id} | Delete publication *PublicationResourceApi* | [**get_publication_by_id**](docs/PublicationResourceApi.md#get_publication_by_id) | **GET** /api/v1/publications/{id} | Get publication by ID @@ -160,6 +164,11 @@ Class | Method | HTTP request | Description - [CompositeCurve](docs/CompositeCurve.md) - [ControlPointCurve](docs/ControlPointCurve.md) - [Coordinate](docs/Coordinate.md) + - [CopasiOptimizationMethod](docs/CopasiOptimizationMethod.md) + - [CopasiOptimizationMethodOptimizationMethodType](docs/CopasiOptimizationMethodOptimizationMethodType.md) + - [CopasiOptimizationParameter](docs/CopasiOptimizationParameter.md) + - [CopasiOptimizationParameterDataType](docs/CopasiOptimizationParameterDataType.md) + - [CopasiOptimizationParameterParamType](docs/CopasiOptimizationParameterParamType.md) - [Curve](docs/Curve.md) - [CurveSelectionInfo](docs/CurveSelectionInfo.md) - [DataIdentifier](docs/DataIdentifier.md) @@ -194,10 +203,19 @@ Class | Method | HTTP request | Description - [MathmodelRef](docs/MathmodelRef.md) - [ModelType](docs/ModelType.md) - [N5ExportRequest](docs/N5ExportRequest.md) + - [OptJobStatus](docs/OptJobStatus.md) + - [OptProblem](docs/OptProblem.md) + - [OptProgressItem](docs/OptProgressItem.md) + - [OptProgressReport](docs/OptProgressReport.md) + - [OptResultSet](docs/OptResultSet.md) + - [OptimizationJobStatus](docs/OptimizationJobStatus.md) - [Origin](docs/Origin.md) + - [ParameterDescription](docs/ParameterDescription.md) - [Publication](docs/Publication.md) - [PublicationInfo](docs/PublicationInfo.md) - [PublishModelsRequest](docs/PublishModelsRequest.md) + - [ReferenceVariable](docs/ReferenceVariable.md) + - [ReferenceVariableReferenceVariableType](docs/ReferenceVariableReferenceVariableType.md) - [SPECIALCLAIM](docs/SPECIALCLAIM.md) - [SampledCurve](docs/SampledCurve.md) - [SchedulerStatus](docs/SchedulerStatus.md) @@ -232,6 +250,8 @@ Class | Method | HTTP request | Description - [VariableMode](docs/VariableMode.md) - [VariableSpecs](docs/VariableSpecs.md) - [VariableType](docs/VariableType.md) + - [Vcellopt](docs/Vcellopt.md) + - [VcelloptStatus](docs/VcelloptStatus.md) - [Version](docs/Version.md) - [VersionFlag](docs/VersionFlag.md) diff --git a/python-restclient/docs/CopasiOptimizationMethod.md b/python-restclient/docs/CopasiOptimizationMethod.md new file mode 100644 index 0000000000..7edd932b14 --- /dev/null +++ b/python-restclient/docs/CopasiOptimizationMethod.md @@ -0,0 +1,29 @@ +# CopasiOptimizationMethod + + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**optimization_method_type** | [**CopasiOptimizationMethodOptimizationMethodType**](CopasiOptimizationMethodOptimizationMethodType.md) | | [optional] +**optimization_parameter** | [**List[CopasiOptimizationParameter]**](CopasiOptimizationParameter.md) | | [optional] + +## Example + +```python +from vcell_client.models.copasi_optimization_method import CopasiOptimizationMethod + +# TODO update the JSON string below +json = "{}" +# create an instance of CopasiOptimizationMethod from a JSON string +copasi_optimization_method_instance = CopasiOptimizationMethod.from_json(json) +# print the JSON string representation of the object +print CopasiOptimizationMethod.to_json() + +# convert the object into a dict +copasi_optimization_method_dict = copasi_optimization_method_instance.to_dict() +# create an instance of CopasiOptimizationMethod from a dict +copasi_optimization_method_form_dict = copasi_optimization_method.from_dict(copasi_optimization_method_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/python-restclient/docs/CopasiOptimizationMethodOptimizationMethodType.md b/python-restclient/docs/CopasiOptimizationMethodOptimizationMethodType.md new file mode 100644 index 0000000000..44fdccf71b --- /dev/null +++ b/python-restclient/docs/CopasiOptimizationMethodOptimizationMethodType.md @@ -0,0 +1,10 @@ +# CopasiOptimizationMethodOptimizationMethodType + + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/python-restclient/docs/CopasiOptimizationParameter.md b/python-restclient/docs/CopasiOptimizationParameter.md new file mode 100644 index 0000000000..c3b7f568a3 --- /dev/null +++ b/python-restclient/docs/CopasiOptimizationParameter.md @@ -0,0 +1,30 @@ +# CopasiOptimizationParameter + + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**data_type** | [**CopasiOptimizationParameterDataType**](CopasiOptimizationParameterDataType.md) | | [optional] +**param_type** | [**CopasiOptimizationParameterParamType**](CopasiOptimizationParameterParamType.md) | | [optional] +**value** | **float** | | [optional] + +## Example + +```python +from vcell_client.models.copasi_optimization_parameter import CopasiOptimizationParameter + +# TODO update the JSON string below +json = "{}" +# create an instance of CopasiOptimizationParameter from a JSON string +copasi_optimization_parameter_instance = CopasiOptimizationParameter.from_json(json) +# print the JSON string representation of the object +print CopasiOptimizationParameter.to_json() + +# convert the object into a dict +copasi_optimization_parameter_dict = copasi_optimization_parameter_instance.to_dict() +# create an instance of CopasiOptimizationParameter from a dict +copasi_optimization_parameter_form_dict = copasi_optimization_parameter.from_dict(copasi_optimization_parameter_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/python-restclient/docs/CopasiOptimizationParameterDataType.md b/python-restclient/docs/CopasiOptimizationParameterDataType.md new file mode 100644 index 0000000000..4181ed8bff --- /dev/null +++ b/python-restclient/docs/CopasiOptimizationParameterDataType.md @@ -0,0 +1,10 @@ +# CopasiOptimizationParameterDataType + + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/python-restclient/docs/CopasiOptimizationParameterParamType.md b/python-restclient/docs/CopasiOptimizationParameterParamType.md new file mode 100644 index 0000000000..a29826ad40 --- /dev/null +++ b/python-restclient/docs/CopasiOptimizationParameterParamType.md @@ -0,0 +1,10 @@ +# CopasiOptimizationParameterParamType + + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/python-restclient/docs/OptJobStatus.md b/python-restclient/docs/OptJobStatus.md new file mode 100644 index 0000000000..887d86b6c3 --- /dev/null +++ b/python-restclient/docs/OptJobStatus.md @@ -0,0 +1,10 @@ +# OptJobStatus + + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/python-restclient/docs/OptProblem.md b/python-restclient/docs/OptProblem.md new file mode 100644 index 0000000000..f1b38c2d39 --- /dev/null +++ b/python-restclient/docs/OptProblem.md @@ -0,0 +1,33 @@ +# OptProblem + + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**copasi_optimization_method** | [**CopasiOptimizationMethod**](CopasiOptimizationMethod.md) | | [optional] +**data_set** | **List[List[float]]** | | [optional] +**math_model_sbml_contents** | **str** | | [optional] +**number_of_optimization_runs** | **int** | | [optional] +**parameter_description_list** | [**List[ParameterDescription]**](ParameterDescription.md) | | [optional] +**reference_variable** | [**List[ReferenceVariable]**](ReferenceVariable.md) | | [optional] + +## Example + +```python +from vcell_client.models.opt_problem import OptProblem + +# TODO update the JSON string below +json = "{}" +# create an instance of OptProblem from a JSON string +opt_problem_instance = OptProblem.from_json(json) +# print the JSON string representation of the object +print OptProblem.to_json() + +# convert the object into a dict +opt_problem_dict = opt_problem_instance.to_dict() +# create an instance of OptProblem from a dict +opt_problem_form_dict = opt_problem.from_dict(opt_problem_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/python-restclient/docs/OptProgressItem.md b/python-restclient/docs/OptProgressItem.md new file mode 100644 index 0000000000..f3325a6d5c --- /dev/null +++ b/python-restclient/docs/OptProgressItem.md @@ -0,0 +1,29 @@ +# OptProgressItem + + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**num_function_evaluations** | **int** | | [optional] +**obj_func_value** | **float** | | [optional] + +## Example + +```python +from vcell_client.models.opt_progress_item import OptProgressItem + +# TODO update the JSON string below +json = "{}" +# create an instance of OptProgressItem from a JSON string +opt_progress_item_instance = OptProgressItem.from_json(json) +# print the JSON string representation of the object +print OptProgressItem.to_json() + +# convert the object into a dict +opt_progress_item_dict = opt_progress_item_instance.to_dict() +# create an instance of OptProgressItem from a dict +opt_progress_item_form_dict = opt_progress_item.from_dict(opt_progress_item_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/python-restclient/docs/OptProgressReport.md b/python-restclient/docs/OptProgressReport.md new file mode 100644 index 0000000000..581fefd79f --- /dev/null +++ b/python-restclient/docs/OptProgressReport.md @@ -0,0 +1,29 @@ +# OptProgressReport + + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**best_param_values** | **Dict[str, float]** | | [optional] +**progress_items** | [**List[OptProgressItem]**](OptProgressItem.md) | | [optional] + +## Example + +```python +from vcell_client.models.opt_progress_report import OptProgressReport + +# TODO update the JSON string below +json = "{}" +# create an instance of OptProgressReport from a JSON string +opt_progress_report_instance = OptProgressReport.from_json(json) +# print the JSON string representation of the object +print OptProgressReport.to_json() + +# convert the object into a dict +opt_progress_report_dict = opt_progress_report_instance.to_dict() +# create an instance of OptProgressReport from a dict +opt_progress_report_form_dict = opt_progress_report.from_dict(opt_progress_report_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/python-restclient/docs/OptResultSet.md b/python-restclient/docs/OptResultSet.md new file mode 100644 index 0000000000..2fc3b26f5e --- /dev/null +++ b/python-restclient/docs/OptResultSet.md @@ -0,0 +1,31 @@ +# OptResultSet + + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**num_function_evaluations** | **int** | | [optional] +**objective_function** | **float** | | [optional] +**opt_parameter_values** | **Dict[str, float]** | | [optional] +**opt_progress_report** | [**OptProgressReport**](OptProgressReport.md) | | [optional] + +## Example + +```python +from vcell_client.models.opt_result_set import OptResultSet + +# TODO update the JSON string below +json = "{}" +# create an instance of OptResultSet from a JSON string +opt_result_set_instance = OptResultSet.from_json(json) +# print the JSON string representation of the object +print OptResultSet.to_json() + +# convert the object into a dict +opt_result_set_dict = opt_result_set_instance.to_dict() +# create an instance of OptResultSet from a dict +opt_result_set_form_dict = opt_result_set.from_dict(opt_result_set_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/python-restclient/docs/OptimizationJobStatus.md b/python-restclient/docs/OptimizationJobStatus.md new file mode 100644 index 0000000000..778d59eecd --- /dev/null +++ b/python-restclient/docs/OptimizationJobStatus.md @@ -0,0 +1,33 @@ +# OptimizationJobStatus + + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **str** | | [optional] +**status** | [**OptJobStatus**](OptJobStatus.md) | | [optional] +**status_message** | **str** | | [optional] +**htc_job_id** | **str** | | [optional] +**progress_report** | [**OptProgressReport**](OptProgressReport.md) | | [optional] +**results** | [**Vcellopt**](Vcellopt.md) | | [optional] + +## Example + +```python +from vcell_client.models.optimization_job_status import OptimizationJobStatus + +# TODO update the JSON string below +json = "{}" +# create an instance of OptimizationJobStatus from a JSON string +optimization_job_status_instance = OptimizationJobStatus.from_json(json) +# print the JSON string representation of the object +print OptimizationJobStatus.to_json() + +# convert the object into a dict +optimization_job_status_dict = optimization_job_status_instance.to_dict() +# create an instance of OptimizationJobStatus from a dict +optimization_job_status_form_dict = optimization_job_status.from_dict(optimization_job_status_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/python-restclient/docs/OptimizationResourceApi.md b/python-restclient/docs/OptimizationResourceApi.md new file mode 100644 index 0000000000..12b9f29542 --- /dev/null +++ b/python-restclient/docs/OptimizationResourceApi.md @@ -0,0 +1,299 @@ +# vcell_client.OptimizationResourceApi + +All URIs are relative to *https://vcell.cam.uchc.edu* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**get_optimization_status**](OptimizationResourceApi.md#get_optimization_status) | **GET** /api/v1/optimization/{optId} | Get status, progress, or results of an optimization job +[**list_optimization_jobs**](OptimizationResourceApi.md#list_optimization_jobs) | **GET** /api/v1/optimization | List optimization jobs for the authenticated user +[**stop_optimization**](OptimizationResourceApi.md#stop_optimization) | **POST** /api/v1/optimization/{optId}/stop | Stop a running optimization job +[**submit_optimization**](OptimizationResourceApi.md#submit_optimization) | **POST** /api/v1/optimization | Submit a new parameter estimation optimization job + + +# **get_optimization_status** +> OptimizationJobStatus get_optimization_status(opt_id) + +Get status, progress, or results of an optimization job + +### Example + +```python +import time +import os +import vcell_client +from vcell_client.models.optimization_job_status import OptimizationJobStatus +from vcell_client.rest import ApiException +from pprint import pprint + +# Defining the host is optional and defaults to https://vcell.cam.uchc.edu +# See configuration.py for a list of all supported configuration parameters. +configuration = vcell_client.Configuration( + host = "https://vcell.cam.uchc.edu" +) + +# The client must configure the authentication and authorization parameters +# in accordance with the API server security policy. +# Examples for each auth method are provided below, use the example that +# satisfies your auth use case. + +# Enter a context with an instance of the API client +with vcell_client.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = vcell_client.OptimizationResourceApi(api_client) + opt_id = 56 # int | + + try: + # Get status, progress, or results of an optimization job + api_response = api_instance.get_optimization_status(opt_id) + print("The response of OptimizationResourceApi->get_optimization_status:\n") + pprint(api_response) + except Exception as e: + print("Exception when calling OptimizationResourceApi->get_optimization_status: %s\n" % e) +``` + + + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **opt_id** | **int**| | + +### Return type + +[**OptimizationJobStatus**](OptimizationJobStatus.md) + +### Authorization + +[openId](../README.md#openId) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**200** | OK | - | +**401** | Not Authenticated | - | +**403** | Not Allowed | - | +**404** | Not found | - | +**500** | Data Access Exception | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **list_optimization_jobs** +> List[OptimizationJobStatus] list_optimization_jobs() + +List optimization jobs for the authenticated user + +### Example + +```python +import time +import os +import vcell_client +from vcell_client.models.optimization_job_status import OptimizationJobStatus +from vcell_client.rest import ApiException +from pprint import pprint + +# Defining the host is optional and defaults to https://vcell.cam.uchc.edu +# See configuration.py for a list of all supported configuration parameters. +configuration = vcell_client.Configuration( + host = "https://vcell.cam.uchc.edu" +) + +# The client must configure the authentication and authorization parameters +# in accordance with the API server security policy. +# Examples for each auth method are provided below, use the example that +# satisfies your auth use case. + +# Enter a context with an instance of the API client +with vcell_client.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = vcell_client.OptimizationResourceApi(api_client) + + try: + # List optimization jobs for the authenticated user + api_response = api_instance.list_optimization_jobs() + print("The response of OptimizationResourceApi->list_optimization_jobs:\n") + pprint(api_response) + except Exception as e: + print("Exception when calling OptimizationResourceApi->list_optimization_jobs: %s\n" % e) +``` + + + +### Parameters +This endpoint does not need any parameter. + +### Return type + +[**List[OptimizationJobStatus]**](OptimizationJobStatus.md) + +### Authorization + +[openId](../README.md#openId) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**200** | OK | - | +**401** | Not Authenticated | - | +**403** | Not Allowed | - | +**500** | Data Access Exception | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **stop_optimization** +> OptimizationJobStatus stop_optimization(opt_id) + +Stop a running optimization job + +### Example + +```python +import time +import os +import vcell_client +from vcell_client.models.optimization_job_status import OptimizationJobStatus +from vcell_client.rest import ApiException +from pprint import pprint + +# Defining the host is optional and defaults to https://vcell.cam.uchc.edu +# See configuration.py for a list of all supported configuration parameters. +configuration = vcell_client.Configuration( + host = "https://vcell.cam.uchc.edu" +) + +# The client must configure the authentication and authorization parameters +# in accordance with the API server security policy. +# Examples for each auth method are provided below, use the example that +# satisfies your auth use case. + +# Enter a context with an instance of the API client +with vcell_client.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = vcell_client.OptimizationResourceApi(api_client) + opt_id = 56 # int | + + try: + # Stop a running optimization job + api_response = api_instance.stop_optimization(opt_id) + print("The response of OptimizationResourceApi->stop_optimization:\n") + pprint(api_response) + except Exception as e: + print("Exception when calling OptimizationResourceApi->stop_optimization: %s\n" % e) +``` + + + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **opt_id** | **int**| | + +### Return type + +[**OptimizationJobStatus**](OptimizationJobStatus.md) + +### Authorization + +[openId](../README.md#openId) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**200** | OK | - | +**401** | Not Authenticated | - | +**403** | Not Allowed | - | +**404** | Not found | - | +**500** | Data Access Exception | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **submit_optimization** +> OptimizationJobStatus submit_optimization(opt_problem=opt_problem) + +Submit a new parameter estimation optimization job + +### Example + +```python +import time +import os +import vcell_client +from vcell_client.models.opt_problem import OptProblem +from vcell_client.models.optimization_job_status import OptimizationJobStatus +from vcell_client.rest import ApiException +from pprint import pprint + +# Defining the host is optional and defaults to https://vcell.cam.uchc.edu +# See configuration.py for a list of all supported configuration parameters. +configuration = vcell_client.Configuration( + host = "https://vcell.cam.uchc.edu" +) + +# The client must configure the authentication and authorization parameters +# in accordance with the API server security policy. +# Examples for each auth method are provided below, use the example that +# satisfies your auth use case. + +# Enter a context with an instance of the API client +with vcell_client.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = vcell_client.OptimizationResourceApi(api_client) + opt_problem = vcell_client.OptProblem() # OptProblem | (optional) + + try: + # Submit a new parameter estimation optimization job + api_response = api_instance.submit_optimization(opt_problem=opt_problem) + print("The response of OptimizationResourceApi->submit_optimization:\n") + pprint(api_response) + except Exception as e: + print("Exception when calling OptimizationResourceApi->submit_optimization: %s\n" % e) +``` + + + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **opt_problem** | [**OptProblem**](OptProblem.md)| | [optional] + +### Return type + +[**OptimizationJobStatus**](OptimizationJobStatus.md) + +### Authorization + +[openId](../README.md#openId) + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**200** | OK | - | +**401** | Not Authenticated | - | +**403** | Not Allowed | - | +**500** | Data Access Exception | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/python-restclient/docs/ParameterDescription.md b/python-restclient/docs/ParameterDescription.md new file mode 100644 index 0000000000..d06c02a706 --- /dev/null +++ b/python-restclient/docs/ParameterDescription.md @@ -0,0 +1,32 @@ +# ParameterDescription + + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**initial_value** | **float** | | [optional] +**max_value** | **float** | | [optional] +**min_value** | **float** | | [optional] +**name** | **str** | | [optional] +**scale** | **float** | | [optional] + +## Example + +```python +from vcell_client.models.parameter_description import ParameterDescription + +# TODO update the JSON string below +json = "{}" +# create an instance of ParameterDescription from a JSON string +parameter_description_instance = ParameterDescription.from_json(json) +# print the JSON string representation of the object +print ParameterDescription.to_json() + +# convert the object into a dict +parameter_description_dict = parameter_description_instance.to_dict() +# create an instance of ParameterDescription from a dict +parameter_description_form_dict = parameter_description.from_dict(parameter_description_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/python-restclient/docs/ReferenceVariable.md b/python-restclient/docs/ReferenceVariable.md new file mode 100644 index 0000000000..17bbaed75d --- /dev/null +++ b/python-restclient/docs/ReferenceVariable.md @@ -0,0 +1,29 @@ +# ReferenceVariable + + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**reference_variable_type** | [**ReferenceVariableReferenceVariableType**](ReferenceVariableReferenceVariableType.md) | | [optional] +**var_name** | **str** | | [optional] + +## Example + +```python +from vcell_client.models.reference_variable import ReferenceVariable + +# TODO update the JSON string below +json = "{}" +# create an instance of ReferenceVariable from a JSON string +reference_variable_instance = ReferenceVariable.from_json(json) +# print the JSON string representation of the object +print ReferenceVariable.to_json() + +# convert the object into a dict +reference_variable_dict = reference_variable_instance.to_dict() +# create an instance of ReferenceVariable from a dict +reference_variable_form_dict = reference_variable.from_dict(reference_variable_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/python-restclient/docs/ReferenceVariableReferenceVariableType.md b/python-restclient/docs/ReferenceVariableReferenceVariableType.md new file mode 100644 index 0000000000..d008ebc5f7 --- /dev/null +++ b/python-restclient/docs/ReferenceVariableReferenceVariableType.md @@ -0,0 +1,10 @@ +# ReferenceVariableReferenceVariableType + + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/python-restclient/docs/Vcellopt.md b/python-restclient/docs/Vcellopt.md new file mode 100644 index 0000000000..d996285ea1 --- /dev/null +++ b/python-restclient/docs/Vcellopt.md @@ -0,0 +1,31 @@ +# Vcellopt + + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**opt_problem** | [**OptProblem**](OptProblem.md) | | [optional] +**opt_result_set** | [**OptResultSet**](OptResultSet.md) | | [optional] +**status** | [**VcelloptStatus**](VcelloptStatus.md) | | [optional] +**status_message** | **str** | | [optional] + +## Example + +```python +from vcell_client.models.vcellopt import Vcellopt + +# TODO update the JSON string below +json = "{}" +# create an instance of Vcellopt from a JSON string +vcellopt_instance = Vcellopt.from_json(json) +# print the JSON string representation of the object +print Vcellopt.to_json() + +# convert the object into a dict +vcellopt_dict = vcellopt_instance.to_dict() +# create an instance of Vcellopt from a dict +vcellopt_form_dict = vcellopt.from_dict(vcellopt_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/python-restclient/docs/VcelloptStatus.md b/python-restclient/docs/VcelloptStatus.md new file mode 100644 index 0000000000..ddd8b79432 --- /dev/null +++ b/python-restclient/docs/VcelloptStatus.md @@ -0,0 +1,10 @@ +# VcelloptStatus + + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/python-restclient/test/test_copasi_optimization_method.py b/python-restclient/test/test_copasi_optimization_method.py new file mode 100644 index 0000000000..fee138aac0 --- /dev/null +++ b/python-restclient/test/test_copasi_optimization_method.py @@ -0,0 +1,59 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest +import datetime + +from vcell_client.models.copasi_optimization_method import CopasiOptimizationMethod + +class TestCopasiOptimizationMethod(unittest.TestCase): + """CopasiOptimizationMethod unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def make_instance(self, include_optional) -> CopasiOptimizationMethod: + """Test CopasiOptimizationMethod + include_option is a boolean, when False only required + params are included, when True both required and + optional params are included """ + # uncomment below to create an instance of `CopasiOptimizationMethod` + """ + model = CopasiOptimizationMethod() + if include_optional: + return CopasiOptimizationMethod( + optimization_method_type = 'SRES', + optimization_parameter = [ + vcell_client.models.copasi_optimization_parameter.CopasiOptimizationParameter( + data_type = 'double', + param_type = 'coolingFactor', + value = 1.337, ) + ] + ) + else: + return CopasiOptimizationMethod( + ) + """ + + def testCopasiOptimizationMethod(self): + """Test CopasiOptimizationMethod""" + # inst_req_only = self.make_instance(include_optional=False) + # inst_req_and_optional = self.make_instance(include_optional=True) + +if __name__ == '__main__': + unittest.main() diff --git a/python-restclient/test/test_copasi_optimization_method_optimization_method_type.py b/python-restclient/test/test_copasi_optimization_method_optimization_method_type.py new file mode 100644 index 0000000000..fa86dd5054 --- /dev/null +++ b/python-restclient/test/test_copasi_optimization_method_optimization_method_type.py @@ -0,0 +1,35 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest +import datetime + +from vcell_client.models.copasi_optimization_method_optimization_method_type import CopasiOptimizationMethodOptimizationMethodType + +class TestCopasiOptimizationMethodOptimizationMethodType(unittest.TestCase): + """CopasiOptimizationMethodOptimizationMethodType unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testCopasiOptimizationMethodOptimizationMethodType(self): + """Test CopasiOptimizationMethodOptimizationMethodType""" + # inst = CopasiOptimizationMethodOptimizationMethodType() + +if __name__ == '__main__': + unittest.main() diff --git a/python-restclient/test/test_copasi_optimization_parameter.py b/python-restclient/test/test_copasi_optimization_parameter.py new file mode 100644 index 0000000000..77b93d27fa --- /dev/null +++ b/python-restclient/test/test_copasi_optimization_parameter.py @@ -0,0 +1,55 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest +import datetime + +from vcell_client.models.copasi_optimization_parameter import CopasiOptimizationParameter + +class TestCopasiOptimizationParameter(unittest.TestCase): + """CopasiOptimizationParameter unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def make_instance(self, include_optional) -> CopasiOptimizationParameter: + """Test CopasiOptimizationParameter + include_option is a boolean, when False only required + params are included, when True both required and + optional params are included """ + # uncomment below to create an instance of `CopasiOptimizationParameter` + """ + model = CopasiOptimizationParameter() + if include_optional: + return CopasiOptimizationParameter( + data_type = 'double', + param_type = 'coolingFactor', + value = 1.337 + ) + else: + return CopasiOptimizationParameter( + ) + """ + + def testCopasiOptimizationParameter(self): + """Test CopasiOptimizationParameter""" + # inst_req_only = self.make_instance(include_optional=False) + # inst_req_and_optional = self.make_instance(include_optional=True) + +if __name__ == '__main__': + unittest.main() diff --git a/python-restclient/test/test_copasi_optimization_parameter_data_type.py b/python-restclient/test/test_copasi_optimization_parameter_data_type.py new file mode 100644 index 0000000000..b21a2dc0d9 --- /dev/null +++ b/python-restclient/test/test_copasi_optimization_parameter_data_type.py @@ -0,0 +1,35 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest +import datetime + +from vcell_client.models.copasi_optimization_parameter_data_type import CopasiOptimizationParameterDataType + +class TestCopasiOptimizationParameterDataType(unittest.TestCase): + """CopasiOptimizationParameterDataType unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testCopasiOptimizationParameterDataType(self): + """Test CopasiOptimizationParameterDataType""" + # inst = CopasiOptimizationParameterDataType() + +if __name__ == '__main__': + unittest.main() diff --git a/python-restclient/test/test_copasi_optimization_parameter_param_type.py b/python-restclient/test/test_copasi_optimization_parameter_param_type.py new file mode 100644 index 0000000000..b2e8955453 --- /dev/null +++ b/python-restclient/test/test_copasi_optimization_parameter_param_type.py @@ -0,0 +1,35 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest +import datetime + +from vcell_client.models.copasi_optimization_parameter_param_type import CopasiOptimizationParameterParamType + +class TestCopasiOptimizationParameterParamType(unittest.TestCase): + """CopasiOptimizationParameterParamType unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testCopasiOptimizationParameterParamType(self): + """Test CopasiOptimizationParameterParamType""" + # inst = CopasiOptimizationParameterParamType() + +if __name__ == '__main__': + unittest.main() diff --git a/python-restclient/test/test_opt_job_status.py b/python-restclient/test/test_opt_job_status.py new file mode 100644 index 0000000000..f02f0b78a1 --- /dev/null +++ b/python-restclient/test/test_opt_job_status.py @@ -0,0 +1,35 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest +import datetime + +from vcell_client.models.opt_job_status import OptJobStatus + +class TestOptJobStatus(unittest.TestCase): + """OptJobStatus unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testOptJobStatus(self): + """Test OptJobStatus""" + # inst = OptJobStatus() + +if __name__ == '__main__': + unittest.main() diff --git a/python-restclient/test/test_opt_problem.py b/python-restclient/test/test_opt_problem.py new file mode 100644 index 0000000000..a4164b94e7 --- /dev/null +++ b/python-restclient/test/test_opt_problem.py @@ -0,0 +1,80 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest +import datetime + +from vcell_client.models.opt_problem import OptProblem + +class TestOptProblem(unittest.TestCase): + """OptProblem unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def make_instance(self, include_optional) -> OptProblem: + """Test OptProblem + include_option is a boolean, when False only required + params are included, when True both required and + optional params are included """ + # uncomment below to create an instance of `OptProblem` + """ + model = OptProblem() + if include_optional: + return OptProblem( + copasi_optimization_method = vcell_client.models.copasi_optimization_method.CopasiOptimizationMethod( + optimization_method_type = 'SRES', + optimization_parameter = [ + vcell_client.models.copasi_optimization_parameter.CopasiOptimizationParameter( + data_type = 'double', + param_type = 'coolingFactor', + value = 1.337, ) + ], ), + data_set = [ + [ + 1.337 + ] + ], + math_model_sbml_contents = '', + number_of_optimization_runs = 56, + parameter_description_list = [ + vcell_client.models.parameter_description.ParameterDescription( + initial_value = 1.337, + max_value = 1.337, + min_value = 1.337, + name = '', + scale = 1.337, ) + ], + reference_variable = [ + vcell_client.models.reference_variable.ReferenceVariable( + reference_variable_type = 'dependent', + var_name = '', ) + ] + ) + else: + return OptProblem( + ) + """ + + def testOptProblem(self): + """Test OptProblem""" + # inst_req_only = self.make_instance(include_optional=False) + # inst_req_and_optional = self.make_instance(include_optional=True) + +if __name__ == '__main__': + unittest.main() diff --git a/python-restclient/test/test_opt_progress_item.py b/python-restclient/test/test_opt_progress_item.py new file mode 100644 index 0000000000..c56fb1a406 --- /dev/null +++ b/python-restclient/test/test_opt_progress_item.py @@ -0,0 +1,54 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest +import datetime + +from vcell_client.models.opt_progress_item import OptProgressItem + +class TestOptProgressItem(unittest.TestCase): + """OptProgressItem unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def make_instance(self, include_optional) -> OptProgressItem: + """Test OptProgressItem + include_option is a boolean, when False only required + params are included, when True both required and + optional params are included """ + # uncomment below to create an instance of `OptProgressItem` + """ + model = OptProgressItem() + if include_optional: + return OptProgressItem( + num_function_evaluations = 56, + obj_func_value = 1.337 + ) + else: + return OptProgressItem( + ) + """ + + def testOptProgressItem(self): + """Test OptProgressItem""" + # inst_req_only = self.make_instance(include_optional=False) + # inst_req_and_optional = self.make_instance(include_optional=True) + +if __name__ == '__main__': + unittest.main() diff --git a/python-restclient/test/test_opt_progress_report.py b/python-restclient/test/test_opt_progress_report.py new file mode 100644 index 0000000000..225a57d777 --- /dev/null +++ b/python-restclient/test/test_opt_progress_report.py @@ -0,0 +1,60 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest +import datetime + +from vcell_client.models.opt_progress_report import OptProgressReport + +class TestOptProgressReport(unittest.TestCase): + """OptProgressReport unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def make_instance(self, include_optional) -> OptProgressReport: + """Test OptProgressReport + include_option is a boolean, when False only required + params are included, when True both required and + optional params are included """ + # uncomment below to create an instance of `OptProgressReport` + """ + model = OptProgressReport() + if include_optional: + return OptProgressReport( + best_param_values = { + 'key' : 1.337 + }, + progress_items = [ + vcell_client.models.opt_progress_item.OptProgressItem( + num_function_evaluations = 56, + obj_func_value = 1.337, ) + ] + ) + else: + return OptProgressReport( + ) + """ + + def testOptProgressReport(self): + """Test OptProgressReport""" + # inst_req_only = self.make_instance(include_optional=False) + # inst_req_and_optional = self.make_instance(include_optional=True) + +if __name__ == '__main__': + unittest.main() diff --git a/python-restclient/test/test_opt_result_set.py b/python-restclient/test/test_opt_result_set.py new file mode 100644 index 0000000000..15dbb9cbf0 --- /dev/null +++ b/python-restclient/test/test_opt_result_set.py @@ -0,0 +1,66 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest +import datetime + +from vcell_client.models.opt_result_set import OptResultSet + +class TestOptResultSet(unittest.TestCase): + """OptResultSet unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def make_instance(self, include_optional) -> OptResultSet: + """Test OptResultSet + include_option is a boolean, when False only required + params are included, when True both required and + optional params are included """ + # uncomment below to create an instance of `OptResultSet` + """ + model = OptResultSet() + if include_optional: + return OptResultSet( + num_function_evaluations = 56, + objective_function = 1.337, + opt_parameter_values = { + 'key' : 1.337 + }, + opt_progress_report = vcell_client.models.opt_progress_report.OptProgressReport( + best_param_values = { + 'key' : 1.337 + }, + progress_items = [ + vcell_client.models.opt_progress_item.OptProgressItem( + num_function_evaluations = 56, + obj_func_value = 1.337, ) + ], ) + ) + else: + return OptResultSet( + ) + """ + + def testOptResultSet(self): + """Test OptResultSet""" + # inst_req_only = self.make_instance(include_optional=False) + # inst_req_and_optional = self.make_instance(include_optional=True) + +if __name__ == '__main__': + unittest.main() diff --git a/python-restclient/test/test_optimization_job_status.py b/python-restclient/test/test_optimization_job_status.py new file mode 100644 index 0000000000..01b8759446 --- /dev/null +++ b/python-restclient/test/test_optimization_job_status.py @@ -0,0 +1,112 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest +import datetime + +from vcell_client.models.optimization_job_status import OptimizationJobStatus + +class TestOptimizationJobStatus(unittest.TestCase): + """OptimizationJobStatus unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def make_instance(self, include_optional) -> OptimizationJobStatus: + """Test OptimizationJobStatus + include_option is a boolean, when False only required + params are included, when True both required and + optional params are included """ + # uncomment below to create an instance of `OptimizationJobStatus` + """ + model = OptimizationJobStatus() + if include_optional: + return OptimizationJobStatus( + id = '', + status = 'SUBMITTED', + status_message = '', + htc_job_id = '', + progress_report = vcell_client.models.opt_progress_report.OptProgressReport( + best_param_values = { + 'key' : 1.337 + }, + progress_items = [ + vcell_client.models.opt_progress_item.OptProgressItem( + num_function_evaluations = 56, + obj_func_value = 1.337, ) + ], ), + results = vcell_client.models.vcellopt.Vcellopt( + opt_problem = vcell_client.models.opt_problem.OptProblem( + copasi_optimization_method = vcell_client.models.copasi_optimization_method.CopasiOptimizationMethod( + optimization_method_type = 'SRES', + optimization_parameter = [ + vcell_client.models.copasi_optimization_parameter.CopasiOptimizationParameter( + data_type = 'double', + param_type = 'coolingFactor', + value = 1.337, ) + ], ), + data_set = [ + [ + 1.337 + ] + ], + math_model_sbml_contents = '', + number_of_optimization_runs = 56, + parameter_description_list = [ + vcell_client.models.parameter_description.ParameterDescription( + initial_value = 1.337, + max_value = 1.337, + min_value = 1.337, + name = '', + scale = 1.337, ) + ], + reference_variable = [ + vcell_client.models.reference_variable.ReferenceVariable( + reference_variable_type = 'dependent', + var_name = '', ) + ], ), + opt_result_set = vcell_client.models.opt_result_set.OptResultSet( + num_function_evaluations = 56, + objective_function = 1.337, + opt_parameter_values = { + 'key' : 1.337 + }, + opt_progress_report = vcell_client.models.opt_progress_report.OptProgressReport( + best_param_values = { + 'key' : 1.337 + }, + progress_items = [ + vcell_client.models.opt_progress_item.OptProgressItem( + num_function_evaluations = 56, + obj_func_value = 1.337, ) + ], ), ), + status = 'complete', + status_message = '', ) + ) + else: + return OptimizationJobStatus( + ) + """ + + def testOptimizationJobStatus(self): + """Test OptimizationJobStatus""" + # inst_req_only = self.make_instance(include_optional=False) + # inst_req_and_optional = self.make_instance(include_optional=True) + +if __name__ == '__main__': + unittest.main() diff --git a/python-restclient/test/test_optimization_resource_api.py b/python-restclient/test/test_optimization_resource_api.py new file mode 100644 index 0000000000..18048bffef --- /dev/null +++ b/python-restclient/test/test_optimization_resource_api.py @@ -0,0 +1,60 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest + +from vcell_client.api.optimization_resource_api import OptimizationResourceApi + + +class TestOptimizationResourceApi(unittest.TestCase): + """OptimizationResourceApi unit test stubs""" + + def setUp(self) -> None: + self.api = OptimizationResourceApi() + + def tearDown(self) -> None: + pass + + def test_get_optimization_status(self) -> None: + """Test case for get_optimization_status + + Get status, progress, or results of an optimization job + """ + pass + + def test_list_optimization_jobs(self) -> None: + """Test case for list_optimization_jobs + + List optimization jobs for the authenticated user + """ + pass + + def test_stop_optimization(self) -> None: + """Test case for stop_optimization + + Stop a running optimization job + """ + pass + + def test_submit_optimization(self) -> None: + """Test case for submit_optimization + + Submit a new parameter estimation optimization job + """ + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/python-restclient/test/test_parameter_description.py b/python-restclient/test/test_parameter_description.py new file mode 100644 index 0000000000..33509cfc74 --- /dev/null +++ b/python-restclient/test/test_parameter_description.py @@ -0,0 +1,57 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest +import datetime + +from vcell_client.models.parameter_description import ParameterDescription + +class TestParameterDescription(unittest.TestCase): + """ParameterDescription unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def make_instance(self, include_optional) -> ParameterDescription: + """Test ParameterDescription + include_option is a boolean, when False only required + params are included, when True both required and + optional params are included """ + # uncomment below to create an instance of `ParameterDescription` + """ + model = ParameterDescription() + if include_optional: + return ParameterDescription( + initial_value = 1.337, + max_value = 1.337, + min_value = 1.337, + name = '', + scale = 1.337 + ) + else: + return ParameterDescription( + ) + """ + + def testParameterDescription(self): + """Test ParameterDescription""" + # inst_req_only = self.make_instance(include_optional=False) + # inst_req_and_optional = self.make_instance(include_optional=True) + +if __name__ == '__main__': + unittest.main() diff --git a/python-restclient/test/test_reference_variable.py b/python-restclient/test/test_reference_variable.py new file mode 100644 index 0000000000..1a7398f026 --- /dev/null +++ b/python-restclient/test/test_reference_variable.py @@ -0,0 +1,54 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest +import datetime + +from vcell_client.models.reference_variable import ReferenceVariable + +class TestReferenceVariable(unittest.TestCase): + """ReferenceVariable unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def make_instance(self, include_optional) -> ReferenceVariable: + """Test ReferenceVariable + include_option is a boolean, when False only required + params are included, when True both required and + optional params are included """ + # uncomment below to create an instance of `ReferenceVariable` + """ + model = ReferenceVariable() + if include_optional: + return ReferenceVariable( + reference_variable_type = 'dependent', + var_name = '' + ) + else: + return ReferenceVariable( + ) + """ + + def testReferenceVariable(self): + """Test ReferenceVariable""" + # inst_req_only = self.make_instance(include_optional=False) + # inst_req_and_optional = self.make_instance(include_optional=True) + +if __name__ == '__main__': + unittest.main() diff --git a/python-restclient/test/test_reference_variable_reference_variable_type.py b/python-restclient/test/test_reference_variable_reference_variable_type.py new file mode 100644 index 0000000000..65c002a603 --- /dev/null +++ b/python-restclient/test/test_reference_variable_reference_variable_type.py @@ -0,0 +1,35 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest +import datetime + +from vcell_client.models.reference_variable_reference_variable_type import ReferenceVariableReferenceVariableType + +class TestReferenceVariableReferenceVariableType(unittest.TestCase): + """ReferenceVariableReferenceVariableType unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testReferenceVariableReferenceVariableType(self): + """Test ReferenceVariableReferenceVariableType""" + # inst = ReferenceVariableReferenceVariableType() + +if __name__ == '__main__': + unittest.main() diff --git a/python-restclient/test/test_vcellopt.py b/python-restclient/test/test_vcellopt.py new file mode 100644 index 0000000000..b9d7f337d8 --- /dev/null +++ b/python-restclient/test/test_vcellopt.py @@ -0,0 +1,98 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest +import datetime + +from vcell_client.models.vcellopt import Vcellopt + +class TestVcellopt(unittest.TestCase): + """Vcellopt unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def make_instance(self, include_optional) -> Vcellopt: + """Test Vcellopt + include_option is a boolean, when False only required + params are included, when True both required and + optional params are included """ + # uncomment below to create an instance of `Vcellopt` + """ + model = Vcellopt() + if include_optional: + return Vcellopt( + opt_problem = vcell_client.models.opt_problem.OptProblem( + copasi_optimization_method = vcell_client.models.copasi_optimization_method.CopasiOptimizationMethod( + optimization_method_type = 'SRES', + optimization_parameter = [ + vcell_client.models.copasi_optimization_parameter.CopasiOptimizationParameter( + data_type = 'double', + param_type = 'coolingFactor', + value = 1.337, ) + ], ), + data_set = [ + [ + 1.337 + ] + ], + math_model_sbml_contents = '', + number_of_optimization_runs = 56, + parameter_description_list = [ + vcell_client.models.parameter_description.ParameterDescription( + initial_value = 1.337, + max_value = 1.337, + min_value = 1.337, + name = '', + scale = 1.337, ) + ], + reference_variable = [ + vcell_client.models.reference_variable.ReferenceVariable( + reference_variable_type = 'dependent', + var_name = '', ) + ], ), + opt_result_set = vcell_client.models.opt_result_set.OptResultSet( + num_function_evaluations = 56, + objective_function = 1.337, + opt_parameter_values = { + 'key' : 1.337 + }, + opt_progress_report = vcell_client.models.opt_progress_report.OptProgressReport( + best_param_values = { + 'key' : 1.337 + }, + progress_items = [ + vcell_client.models.opt_progress_item.OptProgressItem( + num_function_evaluations = 56, + obj_func_value = 1.337, ) + ], ), ), + status = 'complete', + status_message = '' + ) + else: + return Vcellopt( + ) + """ + + def testVcellopt(self): + """Test Vcellopt""" + # inst_req_only = self.make_instance(include_optional=False) + # inst_req_and_optional = self.make_instance(include_optional=True) + +if __name__ == '__main__': + unittest.main() diff --git a/python-restclient/test/test_vcellopt_status.py b/python-restclient/test/test_vcellopt_status.py new file mode 100644 index 0000000000..19abdd05f1 --- /dev/null +++ b/python-restclient/test/test_vcellopt_status.py @@ -0,0 +1,35 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest +import datetime + +from vcell_client.models.vcellopt_status import VcelloptStatus + +class TestVcelloptStatus(unittest.TestCase): + """VcelloptStatus unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testVcelloptStatus(self): + """Test VcelloptStatus""" + # inst = VcelloptStatus() + +if __name__ == '__main__': + unittest.main() diff --git a/python-restclient/vcell_client/__init__.py b/python-restclient/vcell_client/__init__.py index 97b6e5257a..e0658f3265 100644 --- a/python-restclient/vcell_client/__init__.py +++ b/python-restclient/vcell_client/__init__.py @@ -25,6 +25,7 @@ from vcell_client.api.geometry_resource_api import GeometryResourceApi from vcell_client.api.hello_world_api import HelloWorldApi from vcell_client.api.math_model_resource_api import MathModelResourceApi +from vcell_client.api.optimization_resource_api import OptimizationResourceApi from vcell_client.api.publication_resource_api import PublicationResourceApi from vcell_client.api.simulation_resource_api import SimulationResourceApi from vcell_client.api.solver_resource_api import SolverResourceApi @@ -55,6 +56,11 @@ from vcell_client.models.composite_curve import CompositeCurve from vcell_client.models.control_point_curve import ControlPointCurve from vcell_client.models.coordinate import Coordinate +from vcell_client.models.copasi_optimization_method import CopasiOptimizationMethod +from vcell_client.models.copasi_optimization_method_optimization_method_type import CopasiOptimizationMethodOptimizationMethodType +from vcell_client.models.copasi_optimization_parameter import CopasiOptimizationParameter +from vcell_client.models.copasi_optimization_parameter_data_type import CopasiOptimizationParameterDataType +from vcell_client.models.copasi_optimization_parameter_param_type import CopasiOptimizationParameterParamType from vcell_client.models.curve import Curve from vcell_client.models.curve_selection_info import CurveSelectionInfo from vcell_client.models.data_identifier import DataIdentifier @@ -89,10 +95,19 @@ from vcell_client.models.mathmodel_ref import MathmodelRef from vcell_client.models.model_type import ModelType from vcell_client.models.n5_export_request import N5ExportRequest +from vcell_client.models.opt_job_status import OptJobStatus +from vcell_client.models.opt_problem import OptProblem +from vcell_client.models.opt_progress_item import OptProgressItem +from vcell_client.models.opt_progress_report import OptProgressReport +from vcell_client.models.opt_result_set import OptResultSet +from vcell_client.models.optimization_job_status import OptimizationJobStatus from vcell_client.models.origin import Origin +from vcell_client.models.parameter_description import ParameterDescription from vcell_client.models.publication import Publication from vcell_client.models.publication_info import PublicationInfo from vcell_client.models.publish_models_request import PublishModelsRequest +from vcell_client.models.reference_variable import ReferenceVariable +from vcell_client.models.reference_variable_reference_variable_type import ReferenceVariableReferenceVariableType from vcell_client.models.specialclaim import SPECIALCLAIM from vcell_client.models.sampled_curve import SampledCurve from vcell_client.models.scheduler_status import SchedulerStatus @@ -127,5 +142,7 @@ from vcell_client.models.variable_mode import VariableMode from vcell_client.models.variable_specs import VariableSpecs from vcell_client.models.variable_type import VariableType +from vcell_client.models.vcellopt import Vcellopt +from vcell_client.models.vcellopt_status import VcelloptStatus from vcell_client.models.version import Version from vcell_client.models.version_flag import VersionFlag diff --git a/python-restclient/vcell_client/api/__init__.py b/python-restclient/vcell_client/api/__init__.py index f4e375f974..cd1b4be610 100644 --- a/python-restclient/vcell_client/api/__init__.py +++ b/python-restclient/vcell_client/api/__init__.py @@ -8,6 +8,7 @@ from vcell_client.api.geometry_resource_api import GeometryResourceApi from vcell_client.api.hello_world_api import HelloWorldApi from vcell_client.api.math_model_resource_api import MathModelResourceApi +from vcell_client.api.optimization_resource_api import OptimizationResourceApi from vcell_client.api.publication_resource_api import PublicationResourceApi from vcell_client.api.simulation_resource_api import SimulationResourceApi from vcell_client.api.solver_resource_api import SolverResourceApi diff --git a/python-restclient/vcell_client/api/optimization_resource_api.py b/python-restclient/vcell_client/api/optimization_resource_api.py new file mode 100644 index 0000000000..d237702ca0 --- /dev/null +++ b/python-restclient/vcell_client/api/optimization_resource_api.py @@ -0,0 +1,1125 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import io +import warnings + +from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt +from typing import Dict, List, Optional, Tuple, Union, Any + +try: + from typing import Annotated +except ImportError: + from typing_extensions import Annotated + +from pydantic import StrictInt + +from typing import List, Optional + +from vcell_client.models.opt_problem import OptProblem +from vcell_client.models.optimization_job_status import OptimizationJobStatus + +from vcell_client.api_client import ApiClient +from vcell_client.api_response import ApiResponse +from vcell_client.rest import RESTResponseType + + +class OptimizationResourceApi: + """NOTE: This class is auto generated by OpenAPI Generator + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + def __init__(self, api_client=None) -> None: + if api_client is None: + api_client = ApiClient.get_default() + self.api_client = api_client + + + @validate_call + def get_optimization_status( + self, + opt_id: StrictInt, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> OptimizationJobStatus: + """Get status, progress, or results of an optimization job + + + :param opt_id: (required) + :type opt_id: int + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._get_optimization_status_serialize( + opt_id=opt_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "OptimizationJobStatus", + '401': "VCellHTTPError", + '403': None, + '404': "VCellHTTPError", + '500': "VCellHTTPError" + + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + def get_optimization_status_with_http_info( + self, + opt_id: StrictInt, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[OptimizationJobStatus]: + """Get status, progress, or results of an optimization job + + + :param opt_id: (required) + :type opt_id: int + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._get_optimization_status_serialize( + opt_id=opt_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "OptimizationJobStatus", + '401': "VCellHTTPError", + '403': None, + '404': "VCellHTTPError", + '500': "VCellHTTPError" + + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + def get_optimization_status_without_preload_content( + self, + opt_id: StrictInt, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Get status, progress, or results of an optimization job + + + :param opt_id: (required) + :type opt_id: int + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._get_optimization_status_serialize( + opt_id=opt_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "OptimizationJobStatus", + '401': "VCellHTTPError", + '403': None, + '404': "VCellHTTPError", + '500': "VCellHTTPError" + + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _get_optimization_status_serialize( + self, + opt_id, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> Tuple: + + _host = None + + _collection_formats: Dict[str, str] = { + + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[str, str] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + if opt_id is not None: + _path_params['optId'] = opt_id + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + + + # set the HTTP header `Accept` + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + + # authentication setting + _auth_settings: List[str] = [ + 'openId' + ] + + return self.api_client.param_serialize( + method='GET', + resource_path='/api/v1/optimization/{optId}', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + def list_optimization_jobs( + self, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> List[OptimizationJobStatus]: + """List optimization jobs for the authenticated user + + + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._list_optimization_jobs_serialize( + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "List[OptimizationJobStatus]", + '401': "VCellHTTPError", + '403': None, + '500': "VCellHTTPError" + + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + def list_optimization_jobs_with_http_info( + self, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[List[OptimizationJobStatus]]: + """List optimization jobs for the authenticated user + + + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._list_optimization_jobs_serialize( + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "List[OptimizationJobStatus]", + '401': "VCellHTTPError", + '403': None, + '500': "VCellHTTPError" + + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + def list_optimization_jobs_without_preload_content( + self, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """List optimization jobs for the authenticated user + + + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._list_optimization_jobs_serialize( + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "List[OptimizationJobStatus]", + '401': "VCellHTTPError", + '403': None, + '500': "VCellHTTPError" + + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _list_optimization_jobs_serialize( + self, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> Tuple: + + _host = None + + _collection_formats: Dict[str, str] = { + + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[str, str] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + + + # set the HTTP header `Accept` + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + + # authentication setting + _auth_settings: List[str] = [ + 'openId' + ] + + return self.api_client.param_serialize( + method='GET', + resource_path='/api/v1/optimization', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + def stop_optimization( + self, + opt_id: StrictInt, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> OptimizationJobStatus: + """Stop a running optimization job + + + :param opt_id: (required) + :type opt_id: int + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._stop_optimization_serialize( + opt_id=opt_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "OptimizationJobStatus", + '401': "VCellHTTPError", + '403': None, + '404': "VCellHTTPError", + '500': "VCellHTTPError" + + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + def stop_optimization_with_http_info( + self, + opt_id: StrictInt, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[OptimizationJobStatus]: + """Stop a running optimization job + + + :param opt_id: (required) + :type opt_id: int + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._stop_optimization_serialize( + opt_id=opt_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "OptimizationJobStatus", + '401': "VCellHTTPError", + '403': None, + '404': "VCellHTTPError", + '500': "VCellHTTPError" + + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + def stop_optimization_without_preload_content( + self, + opt_id: StrictInt, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Stop a running optimization job + + + :param opt_id: (required) + :type opt_id: int + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._stop_optimization_serialize( + opt_id=opt_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "OptimizationJobStatus", + '401': "VCellHTTPError", + '403': None, + '404': "VCellHTTPError", + '500': "VCellHTTPError" + + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _stop_optimization_serialize( + self, + opt_id, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> Tuple: + + _host = None + + _collection_formats: Dict[str, str] = { + + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[str, str] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + if opt_id is not None: + _path_params['optId'] = opt_id + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + + + # set the HTTP header `Accept` + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + + # authentication setting + _auth_settings: List[str] = [ + 'openId' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/api/v1/optimization/{optId}/stop', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + def submit_optimization( + self, + opt_problem: Optional[OptProblem] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> OptimizationJobStatus: + """Submit a new parameter estimation optimization job + + + :param opt_problem: + :type opt_problem: OptProblem + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._submit_optimization_serialize( + opt_problem=opt_problem, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "OptimizationJobStatus", + '401': "VCellHTTPError", + '403': None, + '500': "VCellHTTPError" + + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + def submit_optimization_with_http_info( + self, + opt_problem: Optional[OptProblem] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[OptimizationJobStatus]: + """Submit a new parameter estimation optimization job + + + :param opt_problem: + :type opt_problem: OptProblem + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._submit_optimization_serialize( + opt_problem=opt_problem, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "OptimizationJobStatus", + '401': "VCellHTTPError", + '403': None, + '500': "VCellHTTPError" + + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + def submit_optimization_without_preload_content( + self, + opt_problem: Optional[OptProblem] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Submit a new parameter estimation optimization job + + + :param opt_problem: + :type opt_problem: OptProblem + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._submit_optimization_serialize( + opt_problem=opt_problem, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "OptimizationJobStatus", + '401': "VCellHTTPError", + '403': None, + '500': "VCellHTTPError" + + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _submit_optimization_serialize( + self, + opt_problem, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> Tuple: + + _host = None + + _collection_formats: Dict[str, str] = { + + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[str, str] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if opt_problem is not None: + _body_params = opt_problem + + + # set the HTTP header `Accept` + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'openId' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/api/v1/optimization', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + diff --git a/python-restclient/vcell_client/models/__init__.py b/python-restclient/vcell_client/models/__init__.py index 62754c6b30..2b35842f98 100644 --- a/python-restclient/vcell_client/models/__init__.py +++ b/python-restclient/vcell_client/models/__init__.py @@ -24,6 +24,11 @@ from vcell_client.models.bio_model_summary import BioModelSummary from vcell_client.models.biomodel_ref import BiomodelRef from vcell_client.models.coordinate import Coordinate +from vcell_client.models.copasi_optimization_method import CopasiOptimizationMethod +from vcell_client.models.copasi_optimization_method_optimization_method_type import CopasiOptimizationMethodOptimizationMethodType +from vcell_client.models.copasi_optimization_parameter import CopasiOptimizationParameter +from vcell_client.models.copasi_optimization_parameter_data_type import CopasiOptimizationParameterDataType +from vcell_client.models.copasi_optimization_parameter_param_type import CopasiOptimizationParameterParamType from vcell_client.models.curve import Curve from vcell_client.models.curve_selection_info import CurveSelectionInfo from vcell_client.models.data_identifier import DataIdentifier @@ -58,10 +63,19 @@ from vcell_client.models.mathmodel_ref import MathmodelRef from vcell_client.models.model_type import ModelType from vcell_client.models.n5_export_request import N5ExportRequest +from vcell_client.models.opt_job_status import OptJobStatus +from vcell_client.models.opt_problem import OptProblem +from vcell_client.models.opt_progress_item import OptProgressItem +from vcell_client.models.opt_progress_report import OptProgressReport +from vcell_client.models.opt_result_set import OptResultSet +from vcell_client.models.optimization_job_status import OptimizationJobStatus from vcell_client.models.origin import Origin +from vcell_client.models.parameter_description import ParameterDescription from vcell_client.models.publication import Publication from vcell_client.models.publication_info import PublicationInfo from vcell_client.models.publish_models_request import PublishModelsRequest +from vcell_client.models.reference_variable import ReferenceVariable +from vcell_client.models.reference_variable_reference_variable_type import ReferenceVariableReferenceVariableType from vcell_client.models.specialclaim import SPECIALCLAIM from vcell_client.models.sampled_curve import SampledCurve from vcell_client.models.scheduler_status import SchedulerStatus @@ -96,6 +110,8 @@ from vcell_client.models.variable_mode import VariableMode from vcell_client.models.variable_specs import VariableSpecs from vcell_client.models.variable_type import VariableType +from vcell_client.models.vcellopt import Vcellopt +from vcell_client.models.vcellopt_status import VcelloptStatus from vcell_client.models.version import Version from vcell_client.models.version_flag import VersionFlag from vcell_client.models.analytic_curve import AnalyticCurve diff --git a/python-restclient/vcell_client/models/copasi_optimization_method.py b/python-restclient/vcell_client/models/copasi_optimization_method.py new file mode 100644 index 0000000000..0843cb9a23 --- /dev/null +++ b/python-restclient/vcell_client/models/copasi_optimization_method.py @@ -0,0 +1,105 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + +from typing import Any, ClassVar, Dict, List, Optional +from pydantic import BaseModel +from pydantic import Field +from vcell_client.models.copasi_optimization_method_optimization_method_type import CopasiOptimizationMethodOptimizationMethodType +from vcell_client.models.copasi_optimization_parameter import CopasiOptimizationParameter +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class CopasiOptimizationMethod(BaseModel): + """ + CopasiOptimizationMethod + """ # noqa: E501 + optimization_method_type: Optional[CopasiOptimizationMethodOptimizationMethodType] = Field(default=None, alias="optimizationMethodType") + optimization_parameter: Optional[List[CopasiOptimizationParameter]] = Field(default=None, alias="optimizationParameter") + __properties: ClassVar[List[str]] = ["optimizationMethodType", "optimizationParameter"] + + model_config = { + "populate_by_name": True, + "validate_assignment": True + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of CopasiOptimizationMethod from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of each item in optimization_parameter (list) + _items = [] + if self.optimization_parameter: + for _item in self.optimization_parameter: + if _item: + _items.append(_item.to_dict()) + _dict['optimizationParameter'] = _items + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of CopasiOptimizationMethod from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + # raise errors for additional fields in the input + for _key in obj.keys(): + if _key not in cls.__properties: + raise ValueError("Error due to additional fields (not defined in CopasiOptimizationMethod) in the input: " + _key) + + _obj = cls.model_validate({ + "optimizationMethodType": obj.get("optimizationMethodType"), + "optimizationParameter": [CopasiOptimizationParameter.from_dict(_item) for _item in obj.get("optimizationParameter")] if obj.get("optimizationParameter") is not None else None + }) + return _obj + + diff --git a/python-restclient/vcell_client/models/copasi_optimization_method_optimization_method_type.py b/python-restclient/vcell_client/models/copasi_optimization_method_optimization_method_type.py new file mode 100644 index 0000000000..402a10e903 --- /dev/null +++ b/python-restclient/vcell_client/models/copasi_optimization_method_optimization_method_type.py @@ -0,0 +1,57 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import json +import pprint +import re # noqa: F401 +from enum import Enum + + + +try: + from typing import Self +except ImportError: + from typing_extensions import Self + + +class CopasiOptimizationMethodOptimizationMethodType(str, Enum): + """ + CopasiOptimizationMethodOptimizationMethodType + """ + + """ + allowed enum values + """ + SRES = 'SRES' + EVOLUTIONARYPROGRAM = 'evolutionaryProgram' + GENETICALGORITHM = 'geneticAlgorithm' + GENETICALGORITHMSR = 'geneticAlgorithmSR' + HOOKEJEEVES = 'hookeJeeves' + LEVENBERGMARQUARDT = 'levenbergMarquardt' + NELDERMEAD = 'nelderMead' + PARTICLESWARM = 'particleSwarm' + PRAXIS = 'praxis' + RANDOMSEARCH = 'randomSearch' + SIMULATEDANNEALING = 'simulatedAnnealing' + STEEPESTDESCENT = 'steepestDescent' + TRUNCATEDNEWTON = 'truncatedNewton' + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of CopasiOptimizationMethodOptimizationMethodType from a JSON string""" + return cls(json.loads(json_str)) + + diff --git a/python-restclient/vcell_client/models/copasi_optimization_parameter.py b/python-restclient/vcell_client/models/copasi_optimization_parameter.py new file mode 100644 index 0000000000..6147452607 --- /dev/null +++ b/python-restclient/vcell_client/models/copasi_optimization_parameter.py @@ -0,0 +1,100 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + +from typing import Any, ClassVar, Dict, List, Optional, Union +from pydantic import BaseModel, StrictFloat, StrictInt +from pydantic import Field +from vcell_client.models.copasi_optimization_parameter_data_type import CopasiOptimizationParameterDataType +from vcell_client.models.copasi_optimization_parameter_param_type import CopasiOptimizationParameterParamType +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class CopasiOptimizationParameter(BaseModel): + """ + CopasiOptimizationParameter + """ # noqa: E501 + data_type: Optional[CopasiOptimizationParameterDataType] = Field(default=None, alias="dataType") + param_type: Optional[CopasiOptimizationParameterParamType] = Field(default=None, alias="paramType") + value: Optional[Union[StrictFloat, StrictInt]] = None + __properties: ClassVar[List[str]] = ["dataType", "paramType", "value"] + + model_config = { + "populate_by_name": True, + "validate_assignment": True + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of CopasiOptimizationParameter from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of CopasiOptimizationParameter from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + # raise errors for additional fields in the input + for _key in obj.keys(): + if _key not in cls.__properties: + raise ValueError("Error due to additional fields (not defined in CopasiOptimizationParameter) in the input: " + _key) + + _obj = cls.model_validate({ + "dataType": obj.get("dataType"), + "paramType": obj.get("paramType"), + "value": obj.get("value") + }) + return _obj + + diff --git a/python-restclient/vcell_client/models/copasi_optimization_parameter_data_type.py b/python-restclient/vcell_client/models/copasi_optimization_parameter_data_type.py new file mode 100644 index 0000000000..e2a0de252a --- /dev/null +++ b/python-restclient/vcell_client/models/copasi_optimization_parameter_data_type.py @@ -0,0 +1,46 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import json +import pprint +import re # noqa: F401 +from enum import Enum + + + +try: + from typing import Self +except ImportError: + from typing_extensions import Self + + +class CopasiOptimizationParameterDataType(str, Enum): + """ + CopasiOptimizationParameterDataType + """ + + """ + allowed enum values + """ + DOUBLE = 'double' + INT = 'int' + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of CopasiOptimizationParameterDataType from a JSON string""" + return cls(json.loads(json_str)) + + diff --git a/python-restclient/vcell_client/models/copasi_optimization_parameter_param_type.py b/python-restclient/vcell_client/models/copasi_optimization_parameter_param_type.py new file mode 100644 index 0000000000..8333d47eac --- /dev/null +++ b/python-restclient/vcell_client/models/copasi_optimization_parameter_param_type.py @@ -0,0 +1,58 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import json +import pprint +import re # noqa: F401 +from enum import Enum + + + +try: + from typing import Self +except ImportError: + from typing_extensions import Self + + +class CopasiOptimizationParameterParamType(str, Enum): + """ + CopasiOptimizationParameterParamType + """ + + """ + allowed enum values + """ + COOLINGFACTOR = 'coolingFactor' + ITERATIONLIMIT = 'iterationLimit' + NUMBEROFGENERATIONS = 'numberOfGenerations' + NUMBEROFITERATIONS = 'numberOfIterations' + PF = 'pf' + POPULATIONSIZE = 'populationSize' + RANDOMNUMBERGENERATOR = 'randomNumberGenerator' + RHO = 'rho' + SCALE = 'scale' + SEED = 'seed' + STARTTEMPERATURE = 'startTemperature' + STDDEVIATION = 'stdDeviation' + SWARMSIZE = 'swarmSize' + TOLERANCE = 'tolerance' + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of CopasiOptimizationParameterParamType from a JSON string""" + return cls(json.loads(json_str)) + + diff --git a/python-restclient/vcell_client/models/opt_job_status.py b/python-restclient/vcell_client/models/opt_job_status.py new file mode 100644 index 0000000000..91a16765fb --- /dev/null +++ b/python-restclient/vcell_client/models/opt_job_status.py @@ -0,0 +1,50 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import json +import pprint +import re # noqa: F401 +from enum import Enum + + + +try: + from typing import Self +except ImportError: + from typing_extensions import Self + + +class OptJobStatus(str, Enum): + """ + OptJobStatus + """ + + """ + allowed enum values + """ + SUBMITTED = 'SUBMITTED' + QUEUED = 'QUEUED' + RUNNING = 'RUNNING' + COMPLETE = 'COMPLETE' + FAILED = 'FAILED' + STOPPED = 'STOPPED' + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of OptJobStatus from a JSON string""" + return cls(json.loads(json_str)) + + diff --git a/python-restclient/vcell_client/models/opt_problem.py b/python-restclient/vcell_client/models/opt_problem.py new file mode 100644 index 0000000000..91908ba3ed --- /dev/null +++ b/python-restclient/vcell_client/models/opt_problem.py @@ -0,0 +1,124 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + +from typing import Any, ClassVar, Dict, List, Optional, Union +from pydantic import BaseModel, StrictFloat, StrictInt, StrictStr +from pydantic import Field +from vcell_client.models.copasi_optimization_method import CopasiOptimizationMethod +from vcell_client.models.parameter_description import ParameterDescription +from vcell_client.models.reference_variable import ReferenceVariable +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class OptProblem(BaseModel): + """ + OptProblem + """ # noqa: E501 + copasi_optimization_method: Optional[CopasiOptimizationMethod] = Field(default=None, alias="copasiOptimizationMethod") + data_set: Optional[List[List[Union[StrictFloat, StrictInt]]]] = Field(default=None, alias="dataSet") + math_model_sbml_contents: Optional[StrictStr] = Field(default=None, alias="mathModelSbmlContents") + number_of_optimization_runs: Optional[StrictInt] = Field(default=None, alias="numberOfOptimizationRuns") + parameter_description_list: Optional[List[ParameterDescription]] = Field(default=None, alias="parameterDescriptionList") + reference_variable: Optional[List[ReferenceVariable]] = Field(default=None, alias="referenceVariable") + __properties: ClassVar[List[str]] = ["copasiOptimizationMethod", "dataSet", "mathModelSbmlContents", "numberOfOptimizationRuns", "parameterDescriptionList", "referenceVariable"] + + model_config = { + "populate_by_name": True, + "validate_assignment": True + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of OptProblem from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of copasi_optimization_method + if self.copasi_optimization_method: + _dict['copasiOptimizationMethod'] = self.copasi_optimization_method.to_dict() + # override the default output from pydantic by calling `to_dict()` of each item in parameter_description_list (list) + _items = [] + if self.parameter_description_list: + for _item in self.parameter_description_list: + if _item: + _items.append(_item.to_dict()) + _dict['parameterDescriptionList'] = _items + # override the default output from pydantic by calling `to_dict()` of each item in reference_variable (list) + _items = [] + if self.reference_variable: + for _item in self.reference_variable: + if _item: + _items.append(_item.to_dict()) + _dict['referenceVariable'] = _items + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of OptProblem from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + # raise errors for additional fields in the input + for _key in obj.keys(): + if _key not in cls.__properties: + raise ValueError("Error due to additional fields (not defined in OptProblem) in the input: " + _key) + + _obj = cls.model_validate({ + "copasiOptimizationMethod": CopasiOptimizationMethod.from_dict(obj.get("copasiOptimizationMethod")) if obj.get("copasiOptimizationMethod") is not None else None, + "dataSet": obj.get("dataSet"), + "mathModelSbmlContents": obj.get("mathModelSbmlContents"), + "numberOfOptimizationRuns": obj.get("numberOfOptimizationRuns"), + "parameterDescriptionList": [ParameterDescription.from_dict(_item) for _item in obj.get("parameterDescriptionList")] if obj.get("parameterDescriptionList") is not None else None, + "referenceVariable": [ReferenceVariable.from_dict(_item) for _item in obj.get("referenceVariable")] if obj.get("referenceVariable") is not None else None + }) + return _obj + + diff --git a/python-restclient/vcell_client/models/opt_progress_item.py b/python-restclient/vcell_client/models/opt_progress_item.py new file mode 100644 index 0000000000..a103b2ed4a --- /dev/null +++ b/python-restclient/vcell_client/models/opt_progress_item.py @@ -0,0 +1,96 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + +from typing import Any, ClassVar, Dict, List, Optional, Union +from pydantic import BaseModel, StrictFloat, StrictInt +from pydantic import Field +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class OptProgressItem(BaseModel): + """ + OptProgressItem + """ # noqa: E501 + num_function_evaluations: Optional[StrictInt] = Field(default=None, alias="numFunctionEvaluations") + obj_func_value: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, alias="objFuncValue") + __properties: ClassVar[List[str]] = ["numFunctionEvaluations", "objFuncValue"] + + model_config = { + "populate_by_name": True, + "validate_assignment": True + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of OptProgressItem from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of OptProgressItem from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + # raise errors for additional fields in the input + for _key in obj.keys(): + if _key not in cls.__properties: + raise ValueError("Error due to additional fields (not defined in OptProgressItem) in the input: " + _key) + + _obj = cls.model_validate({ + "numFunctionEvaluations": obj.get("numFunctionEvaluations"), + "objFuncValue": obj.get("objFuncValue") + }) + return _obj + + diff --git a/python-restclient/vcell_client/models/opt_progress_report.py b/python-restclient/vcell_client/models/opt_progress_report.py new file mode 100644 index 0000000000..1fb72da308 --- /dev/null +++ b/python-restclient/vcell_client/models/opt_progress_report.py @@ -0,0 +1,104 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + +from typing import Any, ClassVar, Dict, List, Optional, Union +from pydantic import BaseModel, StrictFloat, StrictInt +from pydantic import Field +from vcell_client.models.opt_progress_item import OptProgressItem +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class OptProgressReport(BaseModel): + """ + OptProgressReport + """ # noqa: E501 + best_param_values: Optional[Dict[str, Union[StrictFloat, StrictInt]]] = Field(default=None, alias="bestParamValues") + progress_items: Optional[List[OptProgressItem]] = Field(default=None, alias="progressItems") + __properties: ClassVar[List[str]] = ["bestParamValues", "progressItems"] + + model_config = { + "populate_by_name": True, + "validate_assignment": True + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of OptProgressReport from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of each item in progress_items (list) + _items = [] + if self.progress_items: + for _item in self.progress_items: + if _item: + _items.append(_item.to_dict()) + _dict['progressItems'] = _items + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of OptProgressReport from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + # raise errors for additional fields in the input + for _key in obj.keys(): + if _key not in cls.__properties: + raise ValueError("Error due to additional fields (not defined in OptProgressReport) in the input: " + _key) + + _obj = cls.model_validate({ + "bestParamValues": obj.get("bestParamValues"), + "progressItems": [OptProgressItem.from_dict(_item) for _item in obj.get("progressItems")] if obj.get("progressItems") is not None else None + }) + return _obj + + diff --git a/python-restclient/vcell_client/models/opt_result_set.py b/python-restclient/vcell_client/models/opt_result_set.py new file mode 100644 index 0000000000..096b618e87 --- /dev/null +++ b/python-restclient/vcell_client/models/opt_result_set.py @@ -0,0 +1,104 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + +from typing import Any, ClassVar, Dict, List, Optional, Union +from pydantic import BaseModel, StrictFloat, StrictInt +from pydantic import Field +from vcell_client.models.opt_progress_report import OptProgressReport +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class OptResultSet(BaseModel): + """ + OptResultSet + """ # noqa: E501 + num_function_evaluations: Optional[StrictInt] = Field(default=None, alias="numFunctionEvaluations") + objective_function: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, alias="objectiveFunction") + opt_parameter_values: Optional[Dict[str, Union[StrictFloat, StrictInt]]] = Field(default=None, alias="optParameterValues") + opt_progress_report: Optional[OptProgressReport] = Field(default=None, alias="optProgressReport") + __properties: ClassVar[List[str]] = ["numFunctionEvaluations", "objectiveFunction", "optParameterValues", "optProgressReport"] + + model_config = { + "populate_by_name": True, + "validate_assignment": True + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of OptResultSet from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of opt_progress_report + if self.opt_progress_report: + _dict['optProgressReport'] = self.opt_progress_report.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of OptResultSet from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + # raise errors for additional fields in the input + for _key in obj.keys(): + if _key not in cls.__properties: + raise ValueError("Error due to additional fields (not defined in OptResultSet) in the input: " + _key) + + _obj = cls.model_validate({ + "numFunctionEvaluations": obj.get("numFunctionEvaluations"), + "objectiveFunction": obj.get("objectiveFunction"), + "optParameterValues": obj.get("optParameterValues"), + "optProgressReport": OptProgressReport.from_dict(obj.get("optProgressReport")) if obj.get("optProgressReport") is not None else None + }) + return _obj + + diff --git a/python-restclient/vcell_client/models/optimization_job_status.py b/python-restclient/vcell_client/models/optimization_job_status.py new file mode 100644 index 0000000000..87388d8904 --- /dev/null +++ b/python-restclient/vcell_client/models/optimization_job_status.py @@ -0,0 +1,113 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + +from typing import Any, ClassVar, Dict, List, Optional +from pydantic import BaseModel, StrictStr +from pydantic import Field +from vcell_client.models.opt_job_status import OptJobStatus +from vcell_client.models.opt_progress_report import OptProgressReport +from vcell_client.models.vcellopt import Vcellopt +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class OptimizationJobStatus(BaseModel): + """ + OptimizationJobStatus + """ # noqa: E501 + id: Optional[StrictStr] = None + status: Optional[OptJobStatus] = None + status_message: Optional[StrictStr] = Field(default=None, alias="statusMessage") + htc_job_id: Optional[StrictStr] = Field(default=None, alias="htcJobId") + progress_report: Optional[OptProgressReport] = Field(default=None, alias="progressReport") + results: Optional[Vcellopt] = None + __properties: ClassVar[List[str]] = ["id", "status", "statusMessage", "htcJobId", "progressReport", "results"] + + model_config = { + "populate_by_name": True, + "validate_assignment": True + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of OptimizationJobStatus from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of progress_report + if self.progress_report: + _dict['progressReport'] = self.progress_report.to_dict() + # override the default output from pydantic by calling `to_dict()` of results + if self.results: + _dict['results'] = self.results.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of OptimizationJobStatus from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + # raise errors for additional fields in the input + for _key in obj.keys(): + if _key not in cls.__properties: + raise ValueError("Error due to additional fields (not defined in OptimizationJobStatus) in the input: " + _key) + + _obj = cls.model_validate({ + "id": obj.get("id"), + "status": obj.get("status"), + "statusMessage": obj.get("statusMessage"), + "htcJobId": obj.get("htcJobId"), + "progressReport": OptProgressReport.from_dict(obj.get("progressReport")) if obj.get("progressReport") is not None else None, + "results": Vcellopt.from_dict(obj.get("results")) if obj.get("results") is not None else None + }) + return _obj + + diff --git a/python-restclient/vcell_client/models/parameter_description.py b/python-restclient/vcell_client/models/parameter_description.py new file mode 100644 index 0000000000..7f9d4c7c84 --- /dev/null +++ b/python-restclient/vcell_client/models/parameter_description.py @@ -0,0 +1,102 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + +from typing import Any, ClassVar, Dict, List, Optional, Union +from pydantic import BaseModel, StrictFloat, StrictInt, StrictStr +from pydantic import Field +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class ParameterDescription(BaseModel): + """ + ParameterDescription + """ # noqa: E501 + initial_value: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, alias="initialValue") + max_value: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, alias="maxValue") + min_value: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, alias="minValue") + name: Optional[StrictStr] = None + scale: Optional[Union[StrictFloat, StrictInt]] = None + __properties: ClassVar[List[str]] = ["initialValue", "maxValue", "minValue", "name", "scale"] + + model_config = { + "populate_by_name": True, + "validate_assignment": True + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of ParameterDescription from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of ParameterDescription from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + # raise errors for additional fields in the input + for _key in obj.keys(): + if _key not in cls.__properties: + raise ValueError("Error due to additional fields (not defined in ParameterDescription) in the input: " + _key) + + _obj = cls.model_validate({ + "initialValue": obj.get("initialValue"), + "maxValue": obj.get("maxValue"), + "minValue": obj.get("minValue"), + "name": obj.get("name"), + "scale": obj.get("scale") + }) + return _obj + + diff --git a/python-restclient/vcell_client/models/reference_variable.py b/python-restclient/vcell_client/models/reference_variable.py new file mode 100644 index 0000000000..7a29e40af3 --- /dev/null +++ b/python-restclient/vcell_client/models/reference_variable.py @@ -0,0 +1,97 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + +from typing import Any, ClassVar, Dict, List, Optional +from pydantic import BaseModel, StrictStr +from pydantic import Field +from vcell_client.models.reference_variable_reference_variable_type import ReferenceVariableReferenceVariableType +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class ReferenceVariable(BaseModel): + """ + ReferenceVariable + """ # noqa: E501 + reference_variable_type: Optional[ReferenceVariableReferenceVariableType] = Field(default=None, alias="referenceVariableType") + var_name: Optional[StrictStr] = Field(default=None, alias="varName") + __properties: ClassVar[List[str]] = ["referenceVariableType", "varName"] + + model_config = { + "populate_by_name": True, + "validate_assignment": True + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of ReferenceVariable from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of ReferenceVariable from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + # raise errors for additional fields in the input + for _key in obj.keys(): + if _key not in cls.__properties: + raise ValueError("Error due to additional fields (not defined in ReferenceVariable) in the input: " + _key) + + _obj = cls.model_validate({ + "referenceVariableType": obj.get("referenceVariableType"), + "varName": obj.get("varName") + }) + return _obj + + diff --git a/python-restclient/vcell_client/models/reference_variable_reference_variable_type.py b/python-restclient/vcell_client/models/reference_variable_reference_variable_type.py new file mode 100644 index 0000000000..d032702e1e --- /dev/null +++ b/python-restclient/vcell_client/models/reference_variable_reference_variable_type.py @@ -0,0 +1,46 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import json +import pprint +import re # noqa: F401 +from enum import Enum + + + +try: + from typing import Self +except ImportError: + from typing_extensions import Self + + +class ReferenceVariableReferenceVariableType(str, Enum): + """ + ReferenceVariableReferenceVariableType + """ + + """ + allowed enum values + """ + DEPENDENT = 'dependent' + INDEPENDENT = 'independent' + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of ReferenceVariableReferenceVariableType from a JSON string""" + return cls(json.loads(json_str)) + + diff --git a/python-restclient/vcell_client/models/vcellopt.py b/python-restclient/vcell_client/models/vcellopt.py new file mode 100644 index 0000000000..db3d2b2cff --- /dev/null +++ b/python-restclient/vcell_client/models/vcellopt.py @@ -0,0 +1,109 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + +from typing import Any, ClassVar, Dict, List, Optional +from pydantic import BaseModel, StrictStr +from pydantic import Field +from vcell_client.models.opt_problem import OptProblem +from vcell_client.models.opt_result_set import OptResultSet +from vcell_client.models.vcellopt_status import VcelloptStatus +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class Vcellopt(BaseModel): + """ + Vcellopt + """ # noqa: E501 + opt_problem: Optional[OptProblem] = Field(default=None, alias="optProblem") + opt_result_set: Optional[OptResultSet] = Field(default=None, alias="optResultSet") + status: Optional[VcelloptStatus] = None + status_message: Optional[StrictStr] = Field(default=None, alias="statusMessage") + __properties: ClassVar[List[str]] = ["optProblem", "optResultSet", "status", "statusMessage"] + + model_config = { + "populate_by_name": True, + "validate_assignment": True + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of Vcellopt from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of opt_problem + if self.opt_problem: + _dict['optProblem'] = self.opt_problem.to_dict() + # override the default output from pydantic by calling `to_dict()` of opt_result_set + if self.opt_result_set: + _dict['optResultSet'] = self.opt_result_set.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of Vcellopt from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + # raise errors for additional fields in the input + for _key in obj.keys(): + if _key not in cls.__properties: + raise ValueError("Error due to additional fields (not defined in Vcellopt) in the input: " + _key) + + _obj = cls.model_validate({ + "optProblem": OptProblem.from_dict(obj.get("optProblem")) if obj.get("optProblem") is not None else None, + "optResultSet": OptResultSet.from_dict(obj.get("optResultSet")) if obj.get("optResultSet") is not None else None, + "status": obj.get("status"), + "statusMessage": obj.get("statusMessage") + }) + return _obj + + diff --git a/python-restclient/vcell_client/models/vcellopt_status.py b/python-restclient/vcell_client/models/vcellopt_status.py new file mode 100644 index 0000000000..2f4e5ff4af --- /dev/null +++ b/python-restclient/vcell_client/models/vcellopt_status.py @@ -0,0 +1,48 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import json +import pprint +import re # noqa: F401 +from enum import Enum + + + +try: + from typing import Self +except ImportError: + from typing_extensions import Self + + +class VcelloptStatus(str, Enum): + """ + VcelloptStatus + """ + + """ + allowed enum values + """ + COMPLETE = 'complete' + FAILED = 'failed' + QUEUED = 'queued' + RUNNING = 'running' + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of VcelloptStatus from a JSON string""" + return cls(json.loads(json_str)) + + diff --git a/tools/openapi-clients.sh b/tools/openapi-clients.sh new file mode 100755 index 0000000000..afe02de664 --- /dev/null +++ b/tools/openapi-clients.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +set -e + +# +# Regenerate OpenAPI spec and/or generate API clients (Java, Python, TypeScript-Angular). +# +# Usage: +# ./tools/openapi-clients.sh # generate clients from existing tools/openapi.yaml +# ./tools/openapi-clients.sh --update-spec # rebuild vcell-rest, copy spec, then generate clients +# +# The --update-spec flag runs 'mvn clean install -DskipTests' to build vcell-rest, +# which produces the OpenAPI spec via SmallRye OpenAPI, then copies it to tools/openapi.yaml. +# Without the flag, it assumes tools/openapi.yaml is already up to date. +# +# After running, verify downstream compilation: +# mvn compile test-compile -pl vcell-rest -am +# + +generatorCliImage=openapitools/openapi-generator-cli:v7.1.0 + +scriptDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +parentDir="$(dirname "$scriptDir")" + +# Parse arguments +UPDATE_SPEC=false +for arg in "$@"; do + case $arg in + --update-spec) + UPDATE_SPEC=true + ;; + -h|--help) + echo "Usage: $0 [--update-spec]" + echo "" + echo " --update-spec Build vcell-rest and regenerate tools/openapi.yaml before generating clients" + echo " (default) Generate clients from existing tools/openapi.yaml" + exit 0 + ;; + *) + echo "Unknown argument: $arg" + echo "Usage: $0 [--update-spec]" + exit 1 + ;; + esac +done + +# Step 1: Optionally rebuild and update the OpenAPI spec +if [ "$UPDATE_SPEC" = true ]; then + echo "==> Building vcell-rest to generate OpenAPI spec..." + pushd "$parentDir" > /dev/null + mvn clean install dependency:copy-dependencies -DskipTests=true + popd > /dev/null + + echo "==> Copying generated spec to tools/openapi.yaml" + cp "$parentDir/vcell-rest/target/generated/openapi.yaml" "$scriptDir/openapi.yaml" +fi + +# Step 2: Validate the OpenAPI spec +echo "==> Validating openapi.yaml..." +docker run --rm -v "${scriptDir}:/local" ${generatorCliImage} validate -i /local/openapi.yaml --recommend +if [ $? -ne 0 ]; then + echo "openapi.yaml is not valid" + exit 1 +fi + +# Step 3: Generate Java client +echo "==> Generating Java client (vcell-restclient)..." +docker run --rm -v "${parentDir}:/vcell" \ + ${generatorCliImage} \ + generate \ + -g java \ + -i /vcell/tools/openapi.yaml \ + -o /vcell/vcell-restclient \ + -c /vcell/tools/java-config.yaml + +# Apply patch for FieldDataResourceApi +pushd "${parentDir}" > /dev/null || { echo "Failed to cd to ${parentDir}"; exit 1; } +if ! git apply "${scriptDir}/FieldDataResourceApi.patch"; then + echo "Failed to apply FieldDataResourceApi.patch" + exit 1 +fi +popd > /dev/null || { echo "Failed to return to previous directory"; exit 1; } + +# Step 4: Generate Python client +echo "==> Generating Python client (python-restclient)..." +docker run --rm -v "${parentDir}:/vcell" \ + ${generatorCliImage} generate \ + -g python \ + -i /vcell/tools/openapi.yaml \ + -o /vcell/python-restclient \ + -c /vcell/tools/python-config.yaml + +# Apply Python import fixes +"${scriptDir}/python-fix.sh" + +# Step 5: Generate TypeScript-Angular client +echo "==> Generating TypeScript-Angular client (webapp-ng)..." +docker run --rm -v "${parentDir}:/vcell" \ + ${generatorCliImage} generate \ + -g typescript-angular \ + -i /vcell/tools/openapi.yaml \ + -o /vcell/webapp-ng/src/app/core/modules/openapi \ + -c /vcell/tools/typescript-angular-config.yaml + +echo "==> Done. Verify downstream compilation with:" +echo " mvn compile test-compile -pl vcell-rest -am" diff --git a/tools/openapi.yaml b/tools/openapi.yaml index 10dd80a4d2..e70d579b38 100644 --- a/tools/openapi.yaml +++ b/tools/openapi.yaml @@ -1538,6 +1538,158 @@ paths: application/json: schema: $ref: '#/components/schemas/VCellHTTPError' + /api/v1/optimization: + get: + tags: + - Optimization Resource + summary: List optimization jobs for the authenticated user + operationId: listOptimizationJobs + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/OptimizationJobStatus' + "401": + description: Not Authenticated + content: + application/json: + schema: + $ref: '#/components/schemas/VCellHTTPError' + "403": + description: Not Allowed + "500": + description: Data Access Exception + content: + application/json: + schema: + $ref: '#/components/schemas/VCellHTTPError' + security: + - openId: + - user + post: + tags: + - Optimization Resource + summary: Submit a new parameter estimation optimization job + operationId: submitOptimization + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OptProblem' + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/OptimizationJobStatus' + "401": + description: Not Authenticated + content: + application/json: + schema: + $ref: '#/components/schemas/VCellHTTPError' + "403": + description: Not Allowed + "500": + description: Data Access Exception + content: + application/json: + schema: + $ref: '#/components/schemas/VCellHTTPError' + security: + - openId: + - user + /api/v1/optimization/{optId}: + get: + tags: + - Optimization Resource + summary: "Get status, progress, or results of an optimization job" + operationId: getOptimizationStatus + parameters: + - name: optId + in: path + required: true + schema: + format: int64 + type: integer + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/OptimizationJobStatus' + "401": + description: Not Authenticated + content: + application/json: + schema: + $ref: '#/components/schemas/VCellHTTPError' + "403": + description: Not Allowed + "404": + description: Not found + content: + application/json: + schema: + $ref: '#/components/schemas/VCellHTTPError' + "500": + description: Data Access Exception + content: + application/json: + schema: + $ref: '#/components/schemas/VCellHTTPError' + security: + - openId: + - user + /api/v1/optimization/{optId}/stop: + post: + tags: + - Optimization Resource + summary: Stop a running optimization job + operationId: stopOptimization + parameters: + - name: optId + in: path + required: true + schema: + format: int64 + type: integer + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/OptimizationJobStatus' + "401": + description: Not Authenticated + content: + application/json: + schema: + $ref: '#/components/schemas/VCellHTTPError' + "403": + description: Not Allowed + "404": + description: Not found + content: + application/json: + schema: + $ref: '#/components/schemas/VCellHTTPError' + "500": + description: Data Access Exception + content: + application/json: + schema: + $ref: '#/components/schemas/VCellHTTPError' + security: + - openId: + - user /api/v1/publications: get: tags: @@ -2404,6 +2556,63 @@ components: z: format: double type: number + CopasiOptimizationMethod: + type: object + properties: + optimizationMethodType: + $ref: '#/components/schemas/CopasiOptimizationMethodOptimizationMethodType' + optimizationParameter: + type: array + items: + $ref: '#/components/schemas/CopasiOptimizationParameter' + CopasiOptimizationMethodOptimizationMethodType: + enum: + - SRES + - evolutionaryProgram + - geneticAlgorithm + - geneticAlgorithmSR + - hookeJeeves + - levenbergMarquardt + - nelderMead + - particleSwarm + - praxis + - randomSearch + - simulatedAnnealing + - steepestDescent + - truncatedNewton + type: string + CopasiOptimizationParameter: + type: object + properties: + dataType: + $ref: '#/components/schemas/CopasiOptimizationParameterDataType' + paramType: + $ref: '#/components/schemas/CopasiOptimizationParameterParamType' + value: + format: double + type: number + CopasiOptimizationParameterDataType: + enum: + - double + - int + type: string + CopasiOptimizationParameterParamType: + enum: + - coolingFactor + - iterationLimit + - numberOfGenerations + - numberOfIterations + - pf + - populationSize + - randomNumberGenerator + - rho + - scale + - seed + - startTemperature + - stdDeviation + - swarmSize + - tolerance + type: string Curve: required: - type @@ -2980,6 +3189,92 @@ components: $ref: '#/components/schemas/ExportableDataType' datasetName: type: string + OptJobStatus: + enum: + - SUBMITTED + - QUEUED + - RUNNING + - COMPLETE + - FAILED + - STOPPED + type: string + OptProblem: + type: object + properties: + copasiOptimizationMethod: + $ref: '#/components/schemas/CopasiOptimizationMethod' + dataSet: + type: array + items: + type: array + items: + format: double + type: number + mathModelSbmlContents: + type: string + numberOfOptimizationRuns: + format: int32 + type: integer + parameterDescriptionList: + type: array + items: + $ref: '#/components/schemas/ParameterDescription' + referenceVariable: + type: array + items: + $ref: '#/components/schemas/ReferenceVariable' + OptProgressItem: + type: object + properties: + numFunctionEvaluations: + format: int32 + type: integer + objFuncValue: + format: double + type: number + OptProgressReport: + type: object + properties: + bestParamValues: + type: object + additionalProperties: + format: double + type: number + progressItems: + type: array + items: + $ref: '#/components/schemas/OptProgressItem' + OptResultSet: + type: object + properties: + numFunctionEvaluations: + format: int32 + type: integer + objectiveFunction: + format: double + type: number + optParameterValues: + type: object + additionalProperties: + format: double + type: number + optProgressReport: + $ref: '#/components/schemas/OptProgressReport' + OptimizationJobStatus: + type: object + properties: + id: + $ref: '#/components/schemas/KeyValue' + status: + $ref: '#/components/schemas/OptJobStatus' + statusMessage: + type: string + htcJobId: + type: string + progressReport: + $ref: '#/components/schemas/OptProgressReport' + results: + $ref: '#/components/schemas/Vcellopt' Origin: type: object properties: @@ -2998,6 +3293,23 @@ components: type: number xml: attribute: true + ParameterDescription: + type: object + properties: + initialValue: + format: double + type: number + maxValue: + format: double + type: number + minValue: + format: double + type: number + name: + type: string + scale: + format: double + type: number Publication: type: object properties: @@ -3080,6 +3392,18 @@ components: items: format: int64 type: integer + ReferenceVariable: + type: object + properties: + referenceVariableType: + $ref: '#/components/schemas/ReferenceVariableReferenceVariableType' + varName: + type: string + ReferenceVariableReferenceVariableType: + enum: + - dependent + - independent + type: string SPECIAL_CLAIM: enum: - admins @@ -3538,6 +3862,24 @@ components: type: string typeName: type: string + Vcellopt: + type: object + properties: + optProblem: + $ref: '#/components/schemas/OptProblem' + optResultSet: + $ref: '#/components/schemas/OptResultSet' + status: + $ref: '#/components/schemas/VcelloptStatus' + statusMessage: + type: string + VcelloptStatus: + enum: + - complete + - failed + - queued + - running + type: string Version: type: object properties: diff --git a/vcell-restclient/.openapi-generator/FILES b/vcell-restclient/.openapi-generator/FILES index db63317f28..44964ca1cb 100644 --- a/vcell-restclient/.openapi-generator/FILES +++ b/vcell-restclient/.openapi-generator/FILES @@ -15,6 +15,11 @@ docs/BiomodelRef.md docs/CompositeCurve.md docs/ControlPointCurve.md docs/Coordinate.md +docs/CopasiOptimizationMethod.md +docs/CopasiOptimizationMethodOptimizationMethodType.md +docs/CopasiOptimizationParameter.md +docs/CopasiOptimizationParameterDataType.md +docs/CopasiOptimizationParameterParamType.md docs/Curve.md docs/CurveSelectionInfo.md docs/DataIdentifier.md @@ -54,11 +59,21 @@ docs/MathType.md docs/MathmodelRef.md docs/ModelType.md docs/N5ExportRequest.md +docs/OptJobStatus.md +docs/OptProblem.md +docs/OptProgressItem.md +docs/OptProgressReport.md +docs/OptResultSet.md +docs/OptimizationJobStatus.md +docs/OptimizationResourceApi.md docs/Origin.md +docs/ParameterDescription.md docs/Publication.md docs/PublicationInfo.md docs/PublicationResourceApi.md docs/PublishModelsRequest.md +docs/ReferenceVariable.md +docs/ReferenceVariableReferenceVariableType.md docs/SPECIALCLAIM.md docs/SampledCurve.md docs/SchedulerStatus.md @@ -97,6 +112,8 @@ docs/VariableMode.md docs/VariableSpecs.md docs/VariableType.md docs/VcImageResourceApi.md +docs/Vcellopt.md +docs/VcelloptStatus.md docs/Version.md docs/VersionFlag.md src/main/AndroidManifest.xml @@ -116,6 +133,7 @@ src/main/java/org/vcell/restclient/api/FieldDataResourceApi.java src/main/java/org/vcell/restclient/api/GeometryResourceApi.java src/main/java/org/vcell/restclient/api/HelloWorldApi.java src/main/java/org/vcell/restclient/api/MathModelResourceApi.java +src/main/java/org/vcell/restclient/api/OptimizationResourceApi.java src/main/java/org/vcell/restclient/api/PublicationResourceApi.java src/main/java/org/vcell/restclient/api/SimulationResourceApi.java src/main/java/org/vcell/restclient/api/SolverResourceApi.java @@ -134,6 +152,11 @@ src/main/java/org/vcell/restclient/model/BiomodelRef.java src/main/java/org/vcell/restclient/model/CompositeCurve.java src/main/java/org/vcell/restclient/model/ControlPointCurve.java src/main/java/org/vcell/restclient/model/Coordinate.java +src/main/java/org/vcell/restclient/model/CopasiOptimizationMethod.java +src/main/java/org/vcell/restclient/model/CopasiOptimizationMethodOptimizationMethodType.java +src/main/java/org/vcell/restclient/model/CopasiOptimizationParameter.java +src/main/java/org/vcell/restclient/model/CopasiOptimizationParameterDataType.java +src/main/java/org/vcell/restclient/model/CopasiOptimizationParameterParamType.java src/main/java/org/vcell/restclient/model/Curve.java src/main/java/org/vcell/restclient/model/CurveSelectionInfo.java src/main/java/org/vcell/restclient/model/DataIdentifier.java @@ -168,10 +191,19 @@ src/main/java/org/vcell/restclient/model/MathType.java src/main/java/org/vcell/restclient/model/MathmodelRef.java src/main/java/org/vcell/restclient/model/ModelType.java src/main/java/org/vcell/restclient/model/N5ExportRequest.java +src/main/java/org/vcell/restclient/model/OptJobStatus.java +src/main/java/org/vcell/restclient/model/OptProblem.java +src/main/java/org/vcell/restclient/model/OptProgressItem.java +src/main/java/org/vcell/restclient/model/OptProgressReport.java +src/main/java/org/vcell/restclient/model/OptResultSet.java +src/main/java/org/vcell/restclient/model/OptimizationJobStatus.java src/main/java/org/vcell/restclient/model/Origin.java +src/main/java/org/vcell/restclient/model/ParameterDescription.java src/main/java/org/vcell/restclient/model/Publication.java src/main/java/org/vcell/restclient/model/PublicationInfo.java src/main/java/org/vcell/restclient/model/PublishModelsRequest.java +src/main/java/org/vcell/restclient/model/ReferenceVariable.java +src/main/java/org/vcell/restclient/model/ReferenceVariableReferenceVariableType.java src/main/java/org/vcell/restclient/model/SPECIALCLAIM.java src/main/java/org/vcell/restclient/model/SampledCurve.java src/main/java/org/vcell/restclient/model/SchedulerStatus.java @@ -206,5 +238,24 @@ src/main/java/org/vcell/restclient/model/VariableDomain.java src/main/java/org/vcell/restclient/model/VariableMode.java src/main/java/org/vcell/restclient/model/VariableSpecs.java src/main/java/org/vcell/restclient/model/VariableType.java +src/main/java/org/vcell/restclient/model/Vcellopt.java +src/main/java/org/vcell/restclient/model/VcelloptStatus.java src/main/java/org/vcell/restclient/model/Version.java src/main/java/org/vcell/restclient/model/VersionFlag.java +src/test/java/org/vcell/restclient/api/OptimizationResourceApiTest.java +src/test/java/org/vcell/restclient/model/CopasiOptimizationMethodOptimizationMethodTypeTest.java +src/test/java/org/vcell/restclient/model/CopasiOptimizationMethodTest.java +src/test/java/org/vcell/restclient/model/CopasiOptimizationParameterDataTypeTest.java +src/test/java/org/vcell/restclient/model/CopasiOptimizationParameterParamTypeTest.java +src/test/java/org/vcell/restclient/model/CopasiOptimizationParameterTest.java +src/test/java/org/vcell/restclient/model/OptJobStatusTest.java +src/test/java/org/vcell/restclient/model/OptProblemTest.java +src/test/java/org/vcell/restclient/model/OptProgressItemTest.java +src/test/java/org/vcell/restclient/model/OptProgressReportTest.java +src/test/java/org/vcell/restclient/model/OptResultSetTest.java +src/test/java/org/vcell/restclient/model/OptimizationJobStatusTest.java +src/test/java/org/vcell/restclient/model/ParameterDescriptionTest.java +src/test/java/org/vcell/restclient/model/ReferenceVariableReferenceVariableTypeTest.java +src/test/java/org/vcell/restclient/model/ReferenceVariableTest.java +src/test/java/org/vcell/restclient/model/VcelloptStatusTest.java +src/test/java/org/vcell/restclient/model/VcelloptTest.java diff --git a/vcell-restclient/README.md b/vcell-restclient/README.md index 598416b98b..3b7da6daca 100644 --- a/vcell-restclient/README.md +++ b/vcell-restclient/README.md @@ -163,6 +163,14 @@ Class | Method | HTTP request | Description *MathModelResourceApi* | [**getVCMLWithHttpInfo**](docs/MathModelResourceApi.md#getVCMLWithHttpInfo) | **GET** /api/v1/mathModel/{id} | *MathModelResourceApi* | [**saveMathModel**](docs/MathModelResourceApi.md#saveMathModel) | **POST** /api/v1/mathModel | *MathModelResourceApi* | [**saveMathModelWithHttpInfo**](docs/MathModelResourceApi.md#saveMathModelWithHttpInfo) | **POST** /api/v1/mathModel | +*OptimizationResourceApi* | [**getOptimizationStatus**](docs/OptimizationResourceApi.md#getOptimizationStatus) | **GET** /api/v1/optimization/{optId} | Get status, progress, or results of an optimization job +*OptimizationResourceApi* | [**getOptimizationStatusWithHttpInfo**](docs/OptimizationResourceApi.md#getOptimizationStatusWithHttpInfo) | **GET** /api/v1/optimization/{optId} | Get status, progress, or results of an optimization job +*OptimizationResourceApi* | [**listOptimizationJobs**](docs/OptimizationResourceApi.md#listOptimizationJobs) | **GET** /api/v1/optimization | List optimization jobs for the authenticated user +*OptimizationResourceApi* | [**listOptimizationJobsWithHttpInfo**](docs/OptimizationResourceApi.md#listOptimizationJobsWithHttpInfo) | **GET** /api/v1/optimization | List optimization jobs for the authenticated user +*OptimizationResourceApi* | [**stopOptimization**](docs/OptimizationResourceApi.md#stopOptimization) | **POST** /api/v1/optimization/{optId}/stop | Stop a running optimization job +*OptimizationResourceApi* | [**stopOptimizationWithHttpInfo**](docs/OptimizationResourceApi.md#stopOptimizationWithHttpInfo) | **POST** /api/v1/optimization/{optId}/stop | Stop a running optimization job +*OptimizationResourceApi* | [**submitOptimization**](docs/OptimizationResourceApi.md#submitOptimization) | **POST** /api/v1/optimization | Submit a new parameter estimation optimization job +*OptimizationResourceApi* | [**submitOptimizationWithHttpInfo**](docs/OptimizationResourceApi.md#submitOptimizationWithHttpInfo) | **POST** /api/v1/optimization | Submit a new parameter estimation optimization job *PublicationResourceApi* | [**createPublication**](docs/PublicationResourceApi.md#createPublication) | **POST** /api/v1/publications | Create publication *PublicationResourceApi* | [**createPublicationWithHttpInfo**](docs/PublicationResourceApi.md#createPublicationWithHttpInfo) | **POST** /api/v1/publications | Create publication *PublicationResourceApi* | [**deletePublication**](docs/PublicationResourceApi.md#deletePublication) | **DELETE** /api/v1/publications/{id} | Delete publication @@ -231,6 +239,11 @@ Class | Method | HTTP request | Description - [CompositeCurve](docs/CompositeCurve.md) - [ControlPointCurve](docs/ControlPointCurve.md) - [Coordinate](docs/Coordinate.md) + - [CopasiOptimizationMethod](docs/CopasiOptimizationMethod.md) + - [CopasiOptimizationMethodOptimizationMethodType](docs/CopasiOptimizationMethodOptimizationMethodType.md) + - [CopasiOptimizationParameter](docs/CopasiOptimizationParameter.md) + - [CopasiOptimizationParameterDataType](docs/CopasiOptimizationParameterDataType.md) + - [CopasiOptimizationParameterParamType](docs/CopasiOptimizationParameterParamType.md) - [Curve](docs/Curve.md) - [CurveSelectionInfo](docs/CurveSelectionInfo.md) - [DataIdentifier](docs/DataIdentifier.md) @@ -265,10 +278,19 @@ Class | Method | HTTP request | Description - [MathmodelRef](docs/MathmodelRef.md) - [ModelType](docs/ModelType.md) - [N5ExportRequest](docs/N5ExportRequest.md) + - [OptJobStatus](docs/OptJobStatus.md) + - [OptProblem](docs/OptProblem.md) + - [OptProgressItem](docs/OptProgressItem.md) + - [OptProgressReport](docs/OptProgressReport.md) + - [OptResultSet](docs/OptResultSet.md) + - [OptimizationJobStatus](docs/OptimizationJobStatus.md) - [Origin](docs/Origin.md) + - [ParameterDescription](docs/ParameterDescription.md) - [Publication](docs/Publication.md) - [PublicationInfo](docs/PublicationInfo.md) - [PublishModelsRequest](docs/PublishModelsRequest.md) + - [ReferenceVariable](docs/ReferenceVariable.md) + - [ReferenceVariableReferenceVariableType](docs/ReferenceVariableReferenceVariableType.md) - [SPECIALCLAIM](docs/SPECIALCLAIM.md) - [SampledCurve](docs/SampledCurve.md) - [SchedulerStatus](docs/SchedulerStatus.md) @@ -303,6 +325,8 @@ Class | Method | HTTP request | Description - [VariableMode](docs/VariableMode.md) - [VariableSpecs](docs/VariableSpecs.md) - [VariableType](docs/VariableType.md) + - [Vcellopt](docs/Vcellopt.md) + - [VcelloptStatus](docs/VcelloptStatus.md) - [Version](docs/Version.md) - [VersionFlag](docs/VersionFlag.md) diff --git a/vcell-restclient/api/openapi.yaml b/vcell-restclient/api/openapi.yaml index 16c828f0c3..edcf927ace 100644 --- a/vcell-restclient/api/openapi.yaml +++ b/vcell-restclient/api/openapi.yaml @@ -1616,6 +1616,167 @@ paths: tags: - Math Model Resource x-accepts: application/json + /api/v1/optimization: + get: + operationId: listOptimizationJobs + responses: + "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/OptimizationJobStatus' + type: array + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/VCellHTTPError' + description: Not Authenticated + "403": + description: Not Allowed + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/VCellHTTPError' + description: Data Access Exception + security: + - openId: + - user + summary: List optimization jobs for the authenticated user + tags: + - Optimization Resource + x-accepts: application/json + post: + operationId: submitOptimization + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OptProblem' + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/OptimizationJobStatus' + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/VCellHTTPError' + description: Not Authenticated + "403": + description: Not Allowed + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/VCellHTTPError' + description: Data Access Exception + security: + - openId: + - user + summary: Submit a new parameter estimation optimization job + tags: + - Optimization Resource + x-content-type: application/json + x-accepts: application/json + /api/v1/optimization/{optId}: + get: + operationId: getOptimizationStatus + parameters: + - explode: false + in: path + name: optId + required: true + schema: + format: int64 + type: integer + style: simple + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/OptimizationJobStatus' + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/VCellHTTPError' + description: Not Authenticated + "403": + description: Not Allowed + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/VCellHTTPError' + description: Not found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/VCellHTTPError' + description: Data Access Exception + security: + - openId: + - user + summary: "Get status, progress, or results of an optimization job" + tags: + - Optimization Resource + x-accepts: application/json + /api/v1/optimization/{optId}/stop: + post: + operationId: stopOptimization + parameters: + - explode: false + in: path + name: optId + required: true + schema: + format: int64 + type: integer + style: simple + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/OptimizationJobStatus' + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/VCellHTTPError' + description: Not Authenticated + "403": + description: Not Allowed + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/VCellHTTPError' + description: Not found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/VCellHTTPError' + description: Data Access Exception + security: + - openId: + - user + summary: Stop a running optimization job + tags: + - Optimization Resource + x-accepts: application/json /api/v1/publications: get: operationId: getPublications @@ -2734,6 +2895,76 @@ components: format: double type: number type: object + CopasiOptimizationMethod: + example: + optimizationMethodType: null + optimizationParameter: + - paramType: null + dataType: null + value: 5.962133916683182 + - paramType: null + dataType: null + value: 5.962133916683182 + properties: + optimizationMethodType: + $ref: '#/components/schemas/CopasiOptimizationMethodOptimizationMethodType' + optimizationParameter: + items: + $ref: '#/components/schemas/CopasiOptimizationParameter' + type: array + type: object + CopasiOptimizationMethodOptimizationMethodType: + enum: + - SRES + - evolutionaryProgram + - geneticAlgorithm + - geneticAlgorithmSR + - hookeJeeves + - levenbergMarquardt + - nelderMead + - particleSwarm + - praxis + - randomSearch + - simulatedAnnealing + - steepestDescent + - truncatedNewton + type: string + CopasiOptimizationParameter: + example: + paramType: null + dataType: null + value: 5.962133916683182 + properties: + dataType: + $ref: '#/components/schemas/CopasiOptimizationParameterDataType' + paramType: + $ref: '#/components/schemas/CopasiOptimizationParameterParamType' + value: + format: double + type: number + type: object + CopasiOptimizationParameterDataType: + enum: + - double + - int + type: string + CopasiOptimizationParameterParamType: + enum: + - coolingFactor + - iterationLimit + - numberOfGenerations + - numberOfIterations + - pf + - populationSize + - randomNumberGenerator + - rho + - scale + - seed + - startTemperature + - stdDeviation + - swarmSize + - tolerance + type: string Curve: discriminator: mapping: @@ -3994,6 +4225,211 @@ components: datasetName: type: string type: object + OptJobStatus: + enum: + - SUBMITTED + - QUEUED + - RUNNING + - COMPLETE + - FAILED + - STOPPED + type: string + OptProblem: + example: + referenceVariable: + - varName: varName + referenceVariableType: null + - varName: varName + referenceVariableType: null + numberOfOptimizationRuns: 2 + parameterDescriptionList: + - minValue: 3.616076749251911 + maxValue: 9.301444243932576 + name: name + scale: 2.027123023002322 + initialValue: 7.061401241503109 + - minValue: 3.616076749251911 + maxValue: 9.301444243932576 + name: name + scale: 2.027123023002322 + initialValue: 7.061401241503109 + copasiOptimizationMethod: + optimizationMethodType: null + optimizationParameter: + - paramType: null + dataType: null + value: 5.962133916683182 + - paramType: null + dataType: null + value: 5.962133916683182 + mathModelSbmlContents: mathModelSbmlContents + dataSet: + - - 5.637376656633329 + - 5.637376656633329 + - - 5.637376656633329 + - 5.637376656633329 + properties: + copasiOptimizationMethod: + $ref: '#/components/schemas/CopasiOptimizationMethod' + dataSet: + items: + items: + format: double + type: number + type: array + type: array + mathModelSbmlContents: + type: string + numberOfOptimizationRuns: + format: int32 + type: integer + parameterDescriptionList: + items: + $ref: '#/components/schemas/ParameterDescription' + type: array + referenceVariable: + items: + $ref: '#/components/schemas/ReferenceVariable' + type: array + type: object + OptProgressItem: + example: + objFuncValue: 1.4658129805029452 + numFunctionEvaluations: 6 + properties: + numFunctionEvaluations: + format: int32 + type: integer + objFuncValue: + format: double + type: number + type: object + OptProgressReport: + example: + bestParamValues: + key: 0.8008281904610115 + progressItems: + - objFuncValue: 1.4658129805029452 + numFunctionEvaluations: 6 + - objFuncValue: 1.4658129805029452 + numFunctionEvaluations: 6 + properties: + bestParamValues: + additionalProperties: + format: double + type: number + type: object + progressItems: + items: + $ref: '#/components/schemas/OptProgressItem' + type: array + type: object + OptResultSet: + example: + optProgressReport: + bestParamValues: + key: 0.8008281904610115 + progressItems: + - objFuncValue: 1.4658129805029452 + numFunctionEvaluations: 6 + - objFuncValue: 1.4658129805029452 + numFunctionEvaluations: 6 + objectiveFunction: 7.386281948385884 + optParameterValues: + key: 1.2315135367772556 + numFunctionEvaluations: 4 + properties: + numFunctionEvaluations: + format: int32 + type: integer + objectiveFunction: + format: double + type: number + optParameterValues: + additionalProperties: + format: double + type: number + type: object + optProgressReport: + $ref: '#/components/schemas/OptProgressReport' + type: object + OptimizationJobStatus: + example: + htcJobId: htcJobId + progressReport: + bestParamValues: + key: 0.8008281904610115 + progressItems: + - objFuncValue: 1.4658129805029452 + numFunctionEvaluations: 6 + - objFuncValue: 1.4658129805029452 + numFunctionEvaluations: 6 + id: id + results: + optProblem: + referenceVariable: + - varName: varName + referenceVariableType: null + - varName: varName + referenceVariableType: null + numberOfOptimizationRuns: 2 + parameterDescriptionList: + - minValue: 3.616076749251911 + maxValue: 9.301444243932576 + name: name + scale: 2.027123023002322 + initialValue: 7.061401241503109 + - minValue: 3.616076749251911 + maxValue: 9.301444243932576 + name: name + scale: 2.027123023002322 + initialValue: 7.061401241503109 + copasiOptimizationMethod: + optimizationMethodType: null + optimizationParameter: + - paramType: null + dataType: null + value: 5.962133916683182 + - paramType: null + dataType: null + value: 5.962133916683182 + mathModelSbmlContents: mathModelSbmlContents + dataSet: + - - 5.637376656633329 + - 5.637376656633329 + - - 5.637376656633329 + - 5.637376656633329 + optResultSet: + optProgressReport: + bestParamValues: + key: 0.8008281904610115 + progressItems: + - objFuncValue: 1.4658129805029452 + numFunctionEvaluations: 6 + - objFuncValue: 1.4658129805029452 + numFunctionEvaluations: 6 + objectiveFunction: 7.386281948385884 + optParameterValues: + key: 1.2315135367772556 + numFunctionEvaluations: 4 + statusMessage: statusMessage + status: null + statusMessage: statusMessage + status: null + properties: + id: + type: string + status: + $ref: '#/components/schemas/OptJobStatus' + statusMessage: + type: string + htcJobId: + type: string + progressReport: + $ref: '#/components/schemas/OptProgressReport' + results: + $ref: '#/components/schemas/Vcellopt' + type: object Origin: example: x: 5.962133916683182 @@ -4016,6 +4452,29 @@ components: xml: attribute: true type: object + ParameterDescription: + example: + minValue: 3.616076749251911 + maxValue: 9.301444243932576 + name: name + scale: 2.027123023002322 + initialValue: 7.061401241503109 + properties: + initialValue: + format: double + type: number + maxValue: + format: double + type: number + minValue: + format: double + type: number + name: + type: string + scale: + format: double + type: number + type: object Publication: example: date: 2022-03-10T00:00:00.000+00:00 @@ -4167,6 +4626,21 @@ components: type: integer type: array type: object + ReferenceVariable: + example: + varName: varName + referenceVariableType: null + properties: + referenceVariableType: + $ref: '#/components/schemas/ReferenceVariableReferenceVariableType' + varName: + type: string + type: object + ReferenceVariableReferenceVariableType: + enum: + - dependent + - independent + type: string SPECIAL_CLAIM: enum: - admins @@ -5067,6 +5541,73 @@ components: typeName: type: string type: object + Vcellopt: + example: + optProblem: + referenceVariable: + - varName: varName + referenceVariableType: null + - varName: varName + referenceVariableType: null + numberOfOptimizationRuns: 2 + parameterDescriptionList: + - minValue: 3.616076749251911 + maxValue: 9.301444243932576 + name: name + scale: 2.027123023002322 + initialValue: 7.061401241503109 + - minValue: 3.616076749251911 + maxValue: 9.301444243932576 + name: name + scale: 2.027123023002322 + initialValue: 7.061401241503109 + copasiOptimizationMethod: + optimizationMethodType: null + optimizationParameter: + - paramType: null + dataType: null + value: 5.962133916683182 + - paramType: null + dataType: null + value: 5.962133916683182 + mathModelSbmlContents: mathModelSbmlContents + dataSet: + - - 5.637376656633329 + - 5.637376656633329 + - - 5.637376656633329 + - 5.637376656633329 + optResultSet: + optProgressReport: + bestParamValues: + key: 0.8008281904610115 + progressItems: + - objFuncValue: 1.4658129805029452 + numFunctionEvaluations: 6 + - objFuncValue: 1.4658129805029452 + numFunctionEvaluations: 6 + objectiveFunction: 7.386281948385884 + optParameterValues: + key: 1.2315135367772556 + numFunctionEvaluations: 4 + statusMessage: statusMessage + status: null + properties: + optProblem: + $ref: '#/components/schemas/OptProblem' + optResultSet: + $ref: '#/components/schemas/OptResultSet' + status: + $ref: '#/components/schemas/VcelloptStatus' + statusMessage: + type: string + type: object + VcelloptStatus: + enum: + - complete + - failed + - queued + - running + type: string Version: example: date: 2000-01-23T04:56:07.000+00:00 diff --git a/vcell-restclient/docs/CopasiOptimizationMethod.md b/vcell-restclient/docs/CopasiOptimizationMethod.md new file mode 100644 index 0000000000..23272c2259 --- /dev/null +++ b/vcell-restclient/docs/CopasiOptimizationMethod.md @@ -0,0 +1,14 @@ + + +# CopasiOptimizationMethod + + +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +|**optimizationMethodType** | **CopasiOptimizationMethodOptimizationMethodType** | | [optional] | +|**optimizationParameter** | [**List<CopasiOptimizationParameter>**](CopasiOptimizationParameter.md) | | [optional] | + + + diff --git a/vcell-restclient/docs/CopasiOptimizationMethodOptimizationMethodType.md b/vcell-restclient/docs/CopasiOptimizationMethodOptimizationMethodType.md new file mode 100644 index 0000000000..56605ef3d4 --- /dev/null +++ b/vcell-restclient/docs/CopasiOptimizationMethodOptimizationMethodType.md @@ -0,0 +1,35 @@ + + +# CopasiOptimizationMethodOptimizationMethodType + +## Enum + + +* `SRES` (value: `"SRES"`) + +* `EVOLUTIONARYPROGRAM` (value: `"evolutionaryProgram"`) + +* `GENETICALGORITHM` (value: `"geneticAlgorithm"`) + +* `GENETICALGORITHMSR` (value: `"geneticAlgorithmSR"`) + +* `HOOKEJEEVES` (value: `"hookeJeeves"`) + +* `LEVENBERGMARQUARDT` (value: `"levenbergMarquardt"`) + +* `NELDERMEAD` (value: `"nelderMead"`) + +* `PARTICLESWARM` (value: `"particleSwarm"`) + +* `PRAXIS` (value: `"praxis"`) + +* `RANDOMSEARCH` (value: `"randomSearch"`) + +* `SIMULATEDANNEALING` (value: `"simulatedAnnealing"`) + +* `STEEPESTDESCENT` (value: `"steepestDescent"`) + +* `TRUNCATEDNEWTON` (value: `"truncatedNewton"`) + + + diff --git a/vcell-restclient/docs/CopasiOptimizationParameter.md b/vcell-restclient/docs/CopasiOptimizationParameter.md new file mode 100644 index 0000000000..ae157fc1a5 --- /dev/null +++ b/vcell-restclient/docs/CopasiOptimizationParameter.md @@ -0,0 +1,15 @@ + + +# CopasiOptimizationParameter + + +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +|**dataType** | **CopasiOptimizationParameterDataType** | | [optional] | +|**paramType** | **CopasiOptimizationParameterParamType** | | [optional] | +|**value** | **Double** | | [optional] | + + + diff --git a/vcell-restclient/docs/CopasiOptimizationParameterDataType.md b/vcell-restclient/docs/CopasiOptimizationParameterDataType.md new file mode 100644 index 0000000000..1d404da75f --- /dev/null +++ b/vcell-restclient/docs/CopasiOptimizationParameterDataType.md @@ -0,0 +1,13 @@ + + +# CopasiOptimizationParameterDataType + +## Enum + + +* `DOUBLE` (value: `"double"`) + +* `INT` (value: `"int"`) + + + diff --git a/vcell-restclient/docs/CopasiOptimizationParameterParamType.md b/vcell-restclient/docs/CopasiOptimizationParameterParamType.md new file mode 100644 index 0000000000..c7e1b0a6ae --- /dev/null +++ b/vcell-restclient/docs/CopasiOptimizationParameterParamType.md @@ -0,0 +1,37 @@ + + +# CopasiOptimizationParameterParamType + +## Enum + + +* `COOLINGFACTOR` (value: `"coolingFactor"`) + +* `ITERATIONLIMIT` (value: `"iterationLimit"`) + +* `NUMBEROFGENERATIONS` (value: `"numberOfGenerations"`) + +* `NUMBEROFITERATIONS` (value: `"numberOfIterations"`) + +* `PF` (value: `"pf"`) + +* `POPULATIONSIZE` (value: `"populationSize"`) + +* `RANDOMNUMBERGENERATOR` (value: `"randomNumberGenerator"`) + +* `RHO` (value: `"rho"`) + +* `SCALE` (value: `"scale"`) + +* `SEED` (value: `"seed"`) + +* `STARTTEMPERATURE` (value: `"startTemperature"`) + +* `STDDEVIATION` (value: `"stdDeviation"`) + +* `SWARMSIZE` (value: `"swarmSize"`) + +* `TOLERANCE` (value: `"tolerance"`) + + + diff --git a/vcell-restclient/docs/OptJobStatus.md b/vcell-restclient/docs/OptJobStatus.md new file mode 100644 index 0000000000..d25a16cd76 --- /dev/null +++ b/vcell-restclient/docs/OptJobStatus.md @@ -0,0 +1,21 @@ + + +# OptJobStatus + +## Enum + + +* `SUBMITTED` (value: `"SUBMITTED"`) + +* `QUEUED` (value: `"QUEUED"`) + +* `RUNNING` (value: `"RUNNING"`) + +* `COMPLETE` (value: `"COMPLETE"`) + +* `FAILED` (value: `"FAILED"`) + +* `STOPPED` (value: `"STOPPED"`) + + + diff --git a/vcell-restclient/docs/OptProblem.md b/vcell-restclient/docs/OptProblem.md new file mode 100644 index 0000000000..898859e5f2 --- /dev/null +++ b/vcell-restclient/docs/OptProblem.md @@ -0,0 +1,18 @@ + + +# OptProblem + + +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +|**copasiOptimizationMethod** | [**CopasiOptimizationMethod**](CopasiOptimizationMethod.md) | | [optional] | +|**dataSet** | **List<List<Double>>** | | [optional] | +|**mathModelSbmlContents** | **String** | | [optional] | +|**numberOfOptimizationRuns** | **Integer** | | [optional] | +|**parameterDescriptionList** | [**List<ParameterDescription>**](ParameterDescription.md) | | [optional] | +|**referenceVariable** | [**List<ReferenceVariable>**](ReferenceVariable.md) | | [optional] | + + + diff --git a/vcell-restclient/docs/OptProgressItem.md b/vcell-restclient/docs/OptProgressItem.md new file mode 100644 index 0000000000..6f1b822114 --- /dev/null +++ b/vcell-restclient/docs/OptProgressItem.md @@ -0,0 +1,14 @@ + + +# OptProgressItem + + +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +|**numFunctionEvaluations** | **Integer** | | [optional] | +|**objFuncValue** | **Double** | | [optional] | + + + diff --git a/vcell-restclient/docs/OptProgressReport.md b/vcell-restclient/docs/OptProgressReport.md new file mode 100644 index 0000000000..e7f00fa2b9 --- /dev/null +++ b/vcell-restclient/docs/OptProgressReport.md @@ -0,0 +1,14 @@ + + +# OptProgressReport + + +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +|**bestParamValues** | **Map<String, Double>** | | [optional] | +|**progressItems** | [**List<OptProgressItem>**](OptProgressItem.md) | | [optional] | + + + diff --git a/vcell-restclient/docs/OptResultSet.md b/vcell-restclient/docs/OptResultSet.md new file mode 100644 index 0000000000..19ea55c46a --- /dev/null +++ b/vcell-restclient/docs/OptResultSet.md @@ -0,0 +1,16 @@ + + +# OptResultSet + + +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +|**numFunctionEvaluations** | **Integer** | | [optional] | +|**objectiveFunction** | **Double** | | [optional] | +|**optParameterValues** | **Map<String, Double>** | | [optional] | +|**optProgressReport** | [**OptProgressReport**](OptProgressReport.md) | | [optional] | + + + diff --git a/vcell-restclient/docs/OptimizationJobStatus.md b/vcell-restclient/docs/OptimizationJobStatus.md new file mode 100644 index 0000000000..0a8653d200 --- /dev/null +++ b/vcell-restclient/docs/OptimizationJobStatus.md @@ -0,0 +1,18 @@ + + +# OptimizationJobStatus + + +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +|**id** | **String** | | [optional] | +|**status** | **OptJobStatus** | | [optional] | +|**statusMessage** | **String** | | [optional] | +|**htcJobId** | **String** | | [optional] | +|**progressReport** | [**OptProgressReport**](OptProgressReport.md) | | [optional] | +|**results** | [**Vcellopt**](Vcellopt.md) | | [optional] | + + + diff --git a/vcell-restclient/docs/OptimizationResourceApi.md b/vcell-restclient/docs/OptimizationResourceApi.md new file mode 100644 index 0000000000..aaf8b4143f --- /dev/null +++ b/vcell-restclient/docs/OptimizationResourceApi.md @@ -0,0 +1,572 @@ +# OptimizationResourceApi + +All URIs are relative to *https://vcell.cam.uchc.edu* + +| Method | HTTP request | Description | +|------------- | ------------- | -------------| +| [**getOptimizationStatus**](OptimizationResourceApi.md#getOptimizationStatus) | **GET** /api/v1/optimization/{optId} | Get status, progress, or results of an optimization job | +| [**getOptimizationStatusWithHttpInfo**](OptimizationResourceApi.md#getOptimizationStatusWithHttpInfo) | **GET** /api/v1/optimization/{optId} | Get status, progress, or results of an optimization job | +| [**listOptimizationJobs**](OptimizationResourceApi.md#listOptimizationJobs) | **GET** /api/v1/optimization | List optimization jobs for the authenticated user | +| [**listOptimizationJobsWithHttpInfo**](OptimizationResourceApi.md#listOptimizationJobsWithHttpInfo) | **GET** /api/v1/optimization | List optimization jobs for the authenticated user | +| [**stopOptimization**](OptimizationResourceApi.md#stopOptimization) | **POST** /api/v1/optimization/{optId}/stop | Stop a running optimization job | +| [**stopOptimizationWithHttpInfo**](OptimizationResourceApi.md#stopOptimizationWithHttpInfo) | **POST** /api/v1/optimization/{optId}/stop | Stop a running optimization job | +| [**submitOptimization**](OptimizationResourceApi.md#submitOptimization) | **POST** /api/v1/optimization | Submit a new parameter estimation optimization job | +| [**submitOptimizationWithHttpInfo**](OptimizationResourceApi.md#submitOptimizationWithHttpInfo) | **POST** /api/v1/optimization | Submit a new parameter estimation optimization job | + + + +## getOptimizationStatus + +> OptimizationJobStatus getOptimizationStatus(optId) + +Get status, progress, or results of an optimization job + +### Example + +```java +// Import classes: +import org.vcell.restclient.ApiClient; +import org.vcell.restclient.ApiException; +import org.vcell.restclient.Configuration; +import org.vcell.restclient.auth.*; +import org.vcell.restclient.models.*; +import org.vcell.restclient.api.OptimizationResourceApi; + +public class Example { + public static void main(String[] args) { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + defaultClient.setBasePath("https://vcell.cam.uchc.edu"); + + + OptimizationResourceApi apiInstance = new OptimizationResourceApi(defaultClient); + Long optId = 56L; // Long | + try { + OptimizationJobStatus result = apiInstance.getOptimizationStatus(optId); + System.out.println(result); + } catch (ApiException e) { + System.err.println("Exception when calling OptimizationResourceApi#getOptimizationStatus"); + System.err.println("Status code: " + e.getCode()); + System.err.println("Reason: " + e.getResponseBody()); + System.err.println("Response headers: " + e.getResponseHeaders()); + e.printStackTrace(); + } + } +} +``` + +### Parameters + + +| Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------| +| **optId** | **Long**| | | + +### Return type + +[**OptimizationJobStatus**](OptimizationJobStatus.md) + + +### Authorization + +[openId](../README.md#openId) + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +| **200** | OK | - | +| **401** | Not Authenticated | - | +| **403** | Not Allowed | - | +| **404** | Not found | - | +| **500** | Data Access Exception | - | + +## getOptimizationStatusWithHttpInfo + +> ApiResponse getOptimizationStatus getOptimizationStatusWithHttpInfo(optId) + +Get status, progress, or results of an optimization job + +### Example + +```java +// Import classes: +import org.vcell.restclient.ApiClient; +import org.vcell.restclient.ApiException; +import org.vcell.restclient.ApiResponse; +import org.vcell.restclient.Configuration; +import org.vcell.restclient.auth.*; +import org.vcell.restclient.models.*; +import org.vcell.restclient.api.OptimizationResourceApi; + +public class Example { + public static void main(String[] args) { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + defaultClient.setBasePath("https://vcell.cam.uchc.edu"); + + + OptimizationResourceApi apiInstance = new OptimizationResourceApi(defaultClient); + Long optId = 56L; // Long | + try { + ApiResponse response = apiInstance.getOptimizationStatusWithHttpInfo(optId); + System.out.println("Status code: " + response.getStatusCode()); + System.out.println("Response headers: " + response.getHeaders()); + System.out.println("Response body: " + response.getData()); + } catch (ApiException e) { + System.err.println("Exception when calling OptimizationResourceApi#getOptimizationStatus"); + System.err.println("Status code: " + e.getCode()); + System.err.println("Response headers: " + e.getResponseHeaders()); + System.err.println("Reason: " + e.getResponseBody()); + e.printStackTrace(); + } + } +} +``` + +### Parameters + + +| Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------| +| **optId** | **Long**| | | + +### Return type + +ApiResponse<[**OptimizationJobStatus**](OptimizationJobStatus.md)> + + +### Authorization + +[openId](../README.md#openId) + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +| **200** | OK | - | +| **401** | Not Authenticated | - | +| **403** | Not Allowed | - | +| **404** | Not found | - | +| **500** | Data Access Exception | - | + + +## listOptimizationJobs + +> List listOptimizationJobs() + +List optimization jobs for the authenticated user + +### Example + +```java +// Import classes: +import org.vcell.restclient.ApiClient; +import org.vcell.restclient.ApiException; +import org.vcell.restclient.Configuration; +import org.vcell.restclient.auth.*; +import org.vcell.restclient.models.*; +import org.vcell.restclient.api.OptimizationResourceApi; + +public class Example { + public static void main(String[] args) { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + defaultClient.setBasePath("https://vcell.cam.uchc.edu"); + + + OptimizationResourceApi apiInstance = new OptimizationResourceApi(defaultClient); + try { + List result = apiInstance.listOptimizationJobs(); + System.out.println(result); + } catch (ApiException e) { + System.err.println("Exception when calling OptimizationResourceApi#listOptimizationJobs"); + System.err.println("Status code: " + e.getCode()); + System.err.println("Reason: " + e.getResponseBody()); + System.err.println("Response headers: " + e.getResponseHeaders()); + e.printStackTrace(); + } + } +} +``` + +### Parameters + +This endpoint does not need any parameter. + +### Return type + +[**List<OptimizationJobStatus>**](OptimizationJobStatus.md) + + +### Authorization + +[openId](../README.md#openId) + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +| **200** | OK | - | +| **401** | Not Authenticated | - | +| **403** | Not Allowed | - | +| **500** | Data Access Exception | - | + +## listOptimizationJobsWithHttpInfo + +> ApiResponse> listOptimizationJobs listOptimizationJobsWithHttpInfo() + +List optimization jobs for the authenticated user + +### Example + +```java +// Import classes: +import org.vcell.restclient.ApiClient; +import org.vcell.restclient.ApiException; +import org.vcell.restclient.ApiResponse; +import org.vcell.restclient.Configuration; +import org.vcell.restclient.auth.*; +import org.vcell.restclient.models.*; +import org.vcell.restclient.api.OptimizationResourceApi; + +public class Example { + public static void main(String[] args) { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + defaultClient.setBasePath("https://vcell.cam.uchc.edu"); + + + OptimizationResourceApi apiInstance = new OptimizationResourceApi(defaultClient); + try { + ApiResponse> response = apiInstance.listOptimizationJobsWithHttpInfo(); + System.out.println("Status code: " + response.getStatusCode()); + System.out.println("Response headers: " + response.getHeaders()); + System.out.println("Response body: " + response.getData()); + } catch (ApiException e) { + System.err.println("Exception when calling OptimizationResourceApi#listOptimizationJobs"); + System.err.println("Status code: " + e.getCode()); + System.err.println("Response headers: " + e.getResponseHeaders()); + System.err.println("Reason: " + e.getResponseBody()); + e.printStackTrace(); + } + } +} +``` + +### Parameters + +This endpoint does not need any parameter. + +### Return type + +ApiResponse<[**List<OptimizationJobStatus>**](OptimizationJobStatus.md)> + + +### Authorization + +[openId](../README.md#openId) + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +| **200** | OK | - | +| **401** | Not Authenticated | - | +| **403** | Not Allowed | - | +| **500** | Data Access Exception | - | + + +## stopOptimization + +> OptimizationJobStatus stopOptimization(optId) + +Stop a running optimization job + +### Example + +```java +// Import classes: +import org.vcell.restclient.ApiClient; +import org.vcell.restclient.ApiException; +import org.vcell.restclient.Configuration; +import org.vcell.restclient.auth.*; +import org.vcell.restclient.models.*; +import org.vcell.restclient.api.OptimizationResourceApi; + +public class Example { + public static void main(String[] args) { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + defaultClient.setBasePath("https://vcell.cam.uchc.edu"); + + + OptimizationResourceApi apiInstance = new OptimizationResourceApi(defaultClient); + Long optId = 56L; // Long | + try { + OptimizationJobStatus result = apiInstance.stopOptimization(optId); + System.out.println(result); + } catch (ApiException e) { + System.err.println("Exception when calling OptimizationResourceApi#stopOptimization"); + System.err.println("Status code: " + e.getCode()); + System.err.println("Reason: " + e.getResponseBody()); + System.err.println("Response headers: " + e.getResponseHeaders()); + e.printStackTrace(); + } + } +} +``` + +### Parameters + + +| Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------| +| **optId** | **Long**| | | + +### Return type + +[**OptimizationJobStatus**](OptimizationJobStatus.md) + + +### Authorization + +[openId](../README.md#openId) + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +| **200** | OK | - | +| **401** | Not Authenticated | - | +| **403** | Not Allowed | - | +| **404** | Not found | - | +| **500** | Data Access Exception | - | + +## stopOptimizationWithHttpInfo + +> ApiResponse stopOptimization stopOptimizationWithHttpInfo(optId) + +Stop a running optimization job + +### Example + +```java +// Import classes: +import org.vcell.restclient.ApiClient; +import org.vcell.restclient.ApiException; +import org.vcell.restclient.ApiResponse; +import org.vcell.restclient.Configuration; +import org.vcell.restclient.auth.*; +import org.vcell.restclient.models.*; +import org.vcell.restclient.api.OptimizationResourceApi; + +public class Example { + public static void main(String[] args) { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + defaultClient.setBasePath("https://vcell.cam.uchc.edu"); + + + OptimizationResourceApi apiInstance = new OptimizationResourceApi(defaultClient); + Long optId = 56L; // Long | + try { + ApiResponse response = apiInstance.stopOptimizationWithHttpInfo(optId); + System.out.println("Status code: " + response.getStatusCode()); + System.out.println("Response headers: " + response.getHeaders()); + System.out.println("Response body: " + response.getData()); + } catch (ApiException e) { + System.err.println("Exception when calling OptimizationResourceApi#stopOptimization"); + System.err.println("Status code: " + e.getCode()); + System.err.println("Response headers: " + e.getResponseHeaders()); + System.err.println("Reason: " + e.getResponseBody()); + e.printStackTrace(); + } + } +} +``` + +### Parameters + + +| Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------| +| **optId** | **Long**| | | + +### Return type + +ApiResponse<[**OptimizationJobStatus**](OptimizationJobStatus.md)> + + +### Authorization + +[openId](../README.md#openId) + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +| **200** | OK | - | +| **401** | Not Authenticated | - | +| **403** | Not Allowed | - | +| **404** | Not found | - | +| **500** | Data Access Exception | - | + + +## submitOptimization + +> OptimizationJobStatus submitOptimization(optProblem) + +Submit a new parameter estimation optimization job + +### Example + +```java +// Import classes: +import org.vcell.restclient.ApiClient; +import org.vcell.restclient.ApiException; +import org.vcell.restclient.Configuration; +import org.vcell.restclient.auth.*; +import org.vcell.restclient.models.*; +import org.vcell.restclient.api.OptimizationResourceApi; + +public class Example { + public static void main(String[] args) { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + defaultClient.setBasePath("https://vcell.cam.uchc.edu"); + + + OptimizationResourceApi apiInstance = new OptimizationResourceApi(defaultClient); + OptProblem optProblem = new OptProblem(); // OptProblem | + try { + OptimizationJobStatus result = apiInstance.submitOptimization(optProblem); + System.out.println(result); + } catch (ApiException e) { + System.err.println("Exception when calling OptimizationResourceApi#submitOptimization"); + System.err.println("Status code: " + e.getCode()); + System.err.println("Reason: " + e.getResponseBody()); + System.err.println("Response headers: " + e.getResponseHeaders()); + e.printStackTrace(); + } + } +} +``` + +### Parameters + + +| Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------| +| **optProblem** | [**OptProblem**](OptProblem.md)| | [optional] | + +### Return type + +[**OptimizationJobStatus**](OptimizationJobStatus.md) + + +### Authorization + +[openId](../README.md#openId) + +### HTTP request headers + +- **Content-Type**: application/json +- **Accept**: application/json + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +| **200** | OK | - | +| **401** | Not Authenticated | - | +| **403** | Not Allowed | - | +| **500** | Data Access Exception | - | + +## submitOptimizationWithHttpInfo + +> ApiResponse submitOptimization submitOptimizationWithHttpInfo(optProblem) + +Submit a new parameter estimation optimization job + +### Example + +```java +// Import classes: +import org.vcell.restclient.ApiClient; +import org.vcell.restclient.ApiException; +import org.vcell.restclient.ApiResponse; +import org.vcell.restclient.Configuration; +import org.vcell.restclient.auth.*; +import org.vcell.restclient.models.*; +import org.vcell.restclient.api.OptimizationResourceApi; + +public class Example { + public static void main(String[] args) { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + defaultClient.setBasePath("https://vcell.cam.uchc.edu"); + + + OptimizationResourceApi apiInstance = new OptimizationResourceApi(defaultClient); + OptProblem optProblem = new OptProblem(); // OptProblem | + try { + ApiResponse response = apiInstance.submitOptimizationWithHttpInfo(optProblem); + System.out.println("Status code: " + response.getStatusCode()); + System.out.println("Response headers: " + response.getHeaders()); + System.out.println("Response body: " + response.getData()); + } catch (ApiException e) { + System.err.println("Exception when calling OptimizationResourceApi#submitOptimization"); + System.err.println("Status code: " + e.getCode()); + System.err.println("Response headers: " + e.getResponseHeaders()); + System.err.println("Reason: " + e.getResponseBody()); + e.printStackTrace(); + } + } +} +``` + +### Parameters + + +| Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------| +| **optProblem** | [**OptProblem**](OptProblem.md)| | [optional] | + +### Return type + +ApiResponse<[**OptimizationJobStatus**](OptimizationJobStatus.md)> + + +### Authorization + +[openId](../README.md#openId) + +### HTTP request headers + +- **Content-Type**: application/json +- **Accept**: application/json + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +| **200** | OK | - | +| **401** | Not Authenticated | - | +| **403** | Not Allowed | - | +| **500** | Data Access Exception | - | + diff --git a/vcell-restclient/docs/ParameterDescription.md b/vcell-restclient/docs/ParameterDescription.md new file mode 100644 index 0000000000..3d733d5d26 --- /dev/null +++ b/vcell-restclient/docs/ParameterDescription.md @@ -0,0 +1,17 @@ + + +# ParameterDescription + + +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +|**initialValue** | **Double** | | [optional] | +|**maxValue** | **Double** | | [optional] | +|**minValue** | **Double** | | [optional] | +|**name** | **String** | | [optional] | +|**scale** | **Double** | | [optional] | + + + diff --git a/vcell-restclient/docs/ReferenceVariable.md b/vcell-restclient/docs/ReferenceVariable.md new file mode 100644 index 0000000000..307bffdf17 --- /dev/null +++ b/vcell-restclient/docs/ReferenceVariable.md @@ -0,0 +1,14 @@ + + +# ReferenceVariable + + +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +|**referenceVariableType** | **ReferenceVariableReferenceVariableType** | | [optional] | +|**varName** | **String** | | [optional] | + + + diff --git a/vcell-restclient/docs/ReferenceVariableReferenceVariableType.md b/vcell-restclient/docs/ReferenceVariableReferenceVariableType.md new file mode 100644 index 0000000000..6285edaa68 --- /dev/null +++ b/vcell-restclient/docs/ReferenceVariableReferenceVariableType.md @@ -0,0 +1,13 @@ + + +# ReferenceVariableReferenceVariableType + +## Enum + + +* `DEPENDENT` (value: `"dependent"`) + +* `INDEPENDENT` (value: `"independent"`) + + + diff --git a/vcell-restclient/docs/Vcellopt.md b/vcell-restclient/docs/Vcellopt.md new file mode 100644 index 0000000000..24a062acb1 --- /dev/null +++ b/vcell-restclient/docs/Vcellopt.md @@ -0,0 +1,16 @@ + + +# Vcellopt + + +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +|**optProblem** | [**OptProblem**](OptProblem.md) | | [optional] | +|**optResultSet** | [**OptResultSet**](OptResultSet.md) | | [optional] | +|**status** | **VcelloptStatus** | | [optional] | +|**statusMessage** | **String** | | [optional] | + + + diff --git a/vcell-restclient/docs/VcelloptStatus.md b/vcell-restclient/docs/VcelloptStatus.md new file mode 100644 index 0000000000..36e597f713 --- /dev/null +++ b/vcell-restclient/docs/VcelloptStatus.md @@ -0,0 +1,17 @@ + + +# VcelloptStatus + +## Enum + + +* `COMPLETE` (value: `"complete"`) + +* `FAILED` (value: `"failed"`) + +* `QUEUED` (value: `"queued"`) + +* `RUNNING` (value: `"running"`) + + + diff --git a/vcell-restclient/src/main/java/org/vcell/restclient/api/OptimizationResourceApi.java b/vcell-restclient/src/main/java/org/vcell/restclient/api/OptimizationResourceApi.java new file mode 100644 index 0000000000..cf98184e1c --- /dev/null +++ b/vcell-restclient/src/main/java/org/vcell/restclient/api/OptimizationResourceApi.java @@ -0,0 +1,374 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +package org.vcell.restclient.api; + +import org.vcell.restclient.ApiClient; +import org.vcell.restclient.ApiException; +import org.vcell.restclient.ApiResponse; +import org.vcell.restclient.Pair; + +import org.vcell.restclient.model.OptProblem; +import org.vcell.restclient.model.OptimizationJobStatus; +import org.vcell.restclient.model.VCellHTTPError; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.apache.http.HttpEntity; +import org.apache.http.NameValuePair; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; + +import java.io.InputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.net.http.HttpRequest; +import java.nio.channels.Channels; +import java.nio.channels.Pipe; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; + +import java.util.ArrayList; +import java.util.StringJoiner; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen") +public class OptimizationResourceApi { + private final HttpClient memberVarHttpClient; + private final ObjectMapper memberVarObjectMapper; + private final String memberVarBaseUri; + private final Consumer memberVarInterceptor; + private final Duration memberVarReadTimeout; + private final Consumer> memberVarResponseInterceptor; + private final Consumer> memberVarAsyncResponseInterceptor; + + public OptimizationResourceApi() { + this(new ApiClient()); + } + + public OptimizationResourceApi(ApiClient apiClient) { + memberVarHttpClient = apiClient.getHttpClient(); + memberVarObjectMapper = apiClient.getObjectMapper(); + memberVarBaseUri = apiClient.getBaseUri(); + memberVarInterceptor = apiClient.getRequestInterceptor(); + memberVarReadTimeout = apiClient.getReadTimeout(); + memberVarResponseInterceptor = apiClient.getResponseInterceptor(); + memberVarAsyncResponseInterceptor = apiClient.getAsyncResponseInterceptor(); + } + + protected ApiException getApiException(String operationId, HttpResponse response) throws IOException { + String body = response.body() == null ? null : new String(response.body().readAllBytes()); + String message = formatExceptionMessage(operationId, response.statusCode(), body); + return new ApiException(response.statusCode(), message, response.headers(), body); + } + + private String formatExceptionMessage(String operationId, int statusCode, String body) { + if (body == null || body.isEmpty()) { + body = "[no body]"; + } + return operationId + " call failed with: " + statusCode + " - " + body; + } + + /** + * Get status, progress, or results of an optimization job + * + * @param optId (required) + * @return OptimizationJobStatus + * @throws ApiException if fails to make API call + */ + public OptimizationJobStatus getOptimizationStatus(Long optId) throws ApiException { + ApiResponse localVarResponse = getOptimizationStatusWithHttpInfo(optId); + return localVarResponse.getData(); + } + + /** + * Get status, progress, or results of an optimization job + * + * @param optId (required) + * @return ApiResponse<OptimizationJobStatus> + * @throws ApiException if fails to make API call + */ + public ApiResponse getOptimizationStatusWithHttpInfo(Long optId) throws ApiException { + HttpRequest.Builder localVarRequestBuilder = getOptimizationStatusRequestBuilder(optId); + try { + HttpResponse localVarResponse = memberVarHttpClient.send( + localVarRequestBuilder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); + } + try { + if (localVarResponse.statusCode()/ 100 != 2) { + throw getApiException("getOptimizationStatus", localVarResponse); + } + return new ApiResponse( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference() {}) // closes the InputStream + ); + } finally { + } + } catch (IOException e) { + throw new ApiException(e); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + private HttpRequest.Builder getOptimizationStatusRequestBuilder(Long optId) throws ApiException { + // verify the required parameter 'optId' is set + if (optId == null) { + throw new ApiException(400, "Missing the required parameter 'optId' when calling getOptimizationStatus"); + } + + HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); + + String localVarPath = "/api/v1/optimization/{optId}" + .replace("{optId}", ApiClient.urlEncode(optId.toString())); + + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); + + localVarRequestBuilder.header("Accept", "application/json"); + + localVarRequestBuilder.method("GET", HttpRequest.BodyPublishers.noBody()); + if (memberVarReadTimeout != null) { + localVarRequestBuilder.timeout(memberVarReadTimeout); + } + if (memberVarInterceptor != null) { + memberVarInterceptor.accept(localVarRequestBuilder); + } + return localVarRequestBuilder; + } + /** + * List optimization jobs for the authenticated user + * + * @return List<OptimizationJobStatus> + * @throws ApiException if fails to make API call + */ + public List listOptimizationJobs() throws ApiException { + ApiResponse> localVarResponse = listOptimizationJobsWithHttpInfo(); + return localVarResponse.getData(); + } + + /** + * List optimization jobs for the authenticated user + * + * @return ApiResponse<List<OptimizationJobStatus>> + * @throws ApiException if fails to make API call + */ + public ApiResponse> listOptimizationJobsWithHttpInfo() throws ApiException { + HttpRequest.Builder localVarRequestBuilder = listOptimizationJobsRequestBuilder(); + try { + HttpResponse localVarResponse = memberVarHttpClient.send( + localVarRequestBuilder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); + } + try { + if (localVarResponse.statusCode()/ 100 != 2) { + throw getApiException("listOptimizationJobs", localVarResponse); + } + return new ApiResponse>( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference>() {}) // closes the InputStream + ); + } finally { + } + } catch (IOException e) { + throw new ApiException(e); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + private HttpRequest.Builder listOptimizationJobsRequestBuilder() throws ApiException { + + HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); + + String localVarPath = "/api/v1/optimization"; + + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); + + localVarRequestBuilder.header("Accept", "application/json"); + + localVarRequestBuilder.method("GET", HttpRequest.BodyPublishers.noBody()); + if (memberVarReadTimeout != null) { + localVarRequestBuilder.timeout(memberVarReadTimeout); + } + if (memberVarInterceptor != null) { + memberVarInterceptor.accept(localVarRequestBuilder); + } + return localVarRequestBuilder; + } + /** + * Stop a running optimization job + * + * @param optId (required) + * @return OptimizationJobStatus + * @throws ApiException if fails to make API call + */ + public OptimizationJobStatus stopOptimization(Long optId) throws ApiException { + ApiResponse localVarResponse = stopOptimizationWithHttpInfo(optId); + return localVarResponse.getData(); + } + + /** + * Stop a running optimization job + * + * @param optId (required) + * @return ApiResponse<OptimizationJobStatus> + * @throws ApiException if fails to make API call + */ + public ApiResponse stopOptimizationWithHttpInfo(Long optId) throws ApiException { + HttpRequest.Builder localVarRequestBuilder = stopOptimizationRequestBuilder(optId); + try { + HttpResponse localVarResponse = memberVarHttpClient.send( + localVarRequestBuilder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); + } + try { + if (localVarResponse.statusCode()/ 100 != 2) { + throw getApiException("stopOptimization", localVarResponse); + } + return new ApiResponse( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference() {}) // closes the InputStream + ); + } finally { + } + } catch (IOException e) { + throw new ApiException(e); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + private HttpRequest.Builder stopOptimizationRequestBuilder(Long optId) throws ApiException { + // verify the required parameter 'optId' is set + if (optId == null) { + throw new ApiException(400, "Missing the required parameter 'optId' when calling stopOptimization"); + } + + HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); + + String localVarPath = "/api/v1/optimization/{optId}/stop" + .replace("{optId}", ApiClient.urlEncode(optId.toString())); + + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); + + localVarRequestBuilder.header("Accept", "application/json"); + + localVarRequestBuilder.method("POST", HttpRequest.BodyPublishers.noBody()); + if (memberVarReadTimeout != null) { + localVarRequestBuilder.timeout(memberVarReadTimeout); + } + if (memberVarInterceptor != null) { + memberVarInterceptor.accept(localVarRequestBuilder); + } + return localVarRequestBuilder; + } + /** + * Submit a new parameter estimation optimization job + * + * @param optProblem (optional) + * @return OptimizationJobStatus + * @throws ApiException if fails to make API call + */ + public OptimizationJobStatus submitOptimization(OptProblem optProblem) throws ApiException { + ApiResponse localVarResponse = submitOptimizationWithHttpInfo(optProblem); + return localVarResponse.getData(); + } + + /** + * Submit a new parameter estimation optimization job + * + * @param optProblem (optional) + * @return ApiResponse<OptimizationJobStatus> + * @throws ApiException if fails to make API call + */ + public ApiResponse submitOptimizationWithHttpInfo(OptProblem optProblem) throws ApiException { + HttpRequest.Builder localVarRequestBuilder = submitOptimizationRequestBuilder(optProblem); + try { + HttpResponse localVarResponse = memberVarHttpClient.send( + localVarRequestBuilder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); + } + try { + if (localVarResponse.statusCode()/ 100 != 2) { + throw getApiException("submitOptimization", localVarResponse); + } + return new ApiResponse( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference() {}) // closes the InputStream + ); + } finally { + } + } catch (IOException e) { + throw new ApiException(e); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + private HttpRequest.Builder submitOptimizationRequestBuilder(OptProblem optProblem) throws ApiException { + + HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); + + String localVarPath = "/api/v1/optimization"; + + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); + + localVarRequestBuilder.header("Content-Type", "application/json"); + localVarRequestBuilder.header("Accept", "application/json"); + + try { + byte[] localVarPostBody = memberVarObjectMapper.writeValueAsBytes(optProblem); + localVarRequestBuilder.method("POST", HttpRequest.BodyPublishers.ofByteArray(localVarPostBody)); + } catch (IOException e) { + throw new ApiException(e); + } + if (memberVarReadTimeout != null) { + localVarRequestBuilder.timeout(memberVarReadTimeout); + } + if (memberVarInterceptor != null) { + memberVarInterceptor.accept(localVarRequestBuilder); + } + return localVarRequestBuilder; + } +} diff --git a/vcell-restclient/src/main/java/org/vcell/restclient/model/CopasiOptimizationMethod.java b/vcell-restclient/src/main/java/org/vcell/restclient/model/CopasiOptimizationMethod.java new file mode 100644 index 0000000000..fa04816add --- /dev/null +++ b/vcell-restclient/src/main/java/org/vcell/restclient/model/CopasiOptimizationMethod.java @@ -0,0 +1,203 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.StringJoiner; +import java.util.Objects; +import java.util.Map; +import java.util.HashMap; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.vcell.restclient.model.CopasiOptimizationMethodOptimizationMethodType; +import org.vcell.restclient.model.CopasiOptimizationParameter; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + + +/** + * CopasiOptimizationMethod + */ +@JsonPropertyOrder({ + CopasiOptimizationMethod.JSON_PROPERTY_OPTIMIZATION_METHOD_TYPE, + CopasiOptimizationMethod.JSON_PROPERTY_OPTIMIZATION_PARAMETER +}) +@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen") +public class CopasiOptimizationMethod { + public static final String JSON_PROPERTY_OPTIMIZATION_METHOD_TYPE = "optimizationMethodType"; + private CopasiOptimizationMethodOptimizationMethodType optimizationMethodType; + + public static final String JSON_PROPERTY_OPTIMIZATION_PARAMETER = "optimizationParameter"; + private List optimizationParameter; + + public CopasiOptimizationMethod() { + } + + public CopasiOptimizationMethod optimizationMethodType(CopasiOptimizationMethodOptimizationMethodType optimizationMethodType) { + this.optimizationMethodType = optimizationMethodType; + return this; + } + + /** + * Get optimizationMethodType + * @return optimizationMethodType + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_OPTIMIZATION_METHOD_TYPE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public CopasiOptimizationMethodOptimizationMethodType getOptimizationMethodType() { + return optimizationMethodType; + } + + + @JsonProperty(JSON_PROPERTY_OPTIMIZATION_METHOD_TYPE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setOptimizationMethodType(CopasiOptimizationMethodOptimizationMethodType optimizationMethodType) { + this.optimizationMethodType = optimizationMethodType; + } + + + public CopasiOptimizationMethod optimizationParameter(List optimizationParameter) { + this.optimizationParameter = optimizationParameter; + return this; + } + + public CopasiOptimizationMethod addOptimizationParameterItem(CopasiOptimizationParameter optimizationParameterItem) { + if (this.optimizationParameter == null) { + this.optimizationParameter = new ArrayList<>(); + } + this.optimizationParameter.add(optimizationParameterItem); + return this; + } + + /** + * Get optimizationParameter + * @return optimizationParameter + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_OPTIMIZATION_PARAMETER) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public List getOptimizationParameter() { + return optimizationParameter; + } + + + @JsonProperty(JSON_PROPERTY_OPTIMIZATION_PARAMETER) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setOptimizationParameter(List optimizationParameter) { + this.optimizationParameter = optimizationParameter; + } + + + /** + * Return true if this CopasiOptimizationMethod object is equal to o. + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CopasiOptimizationMethod copasiOptimizationMethod = (CopasiOptimizationMethod) o; + return Objects.equals(this.optimizationMethodType, copasiOptimizationMethod.optimizationMethodType) && + Objects.equals(this.optimizationParameter, copasiOptimizationMethod.optimizationParameter); + } + + @Override + public int hashCode() { + return Objects.hash(optimizationMethodType, optimizationParameter); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class CopasiOptimizationMethod {\n"); + sb.append(" optimizationMethodType: ").append(toIndentedString(optimizationMethodType)).append("\n"); + sb.append(" optimizationParameter: ").append(toIndentedString(optimizationParameter)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + /** + * Convert the instance into URL query string. + * + * @return URL query string + */ + public String toUrlQueryString() { + return toUrlQueryString(null); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + String suffix = ""; + String containerSuffix = ""; + String containerPrefix = ""; + if (prefix == null) { + // style=form, explode=true, e.g. /pet?name=cat&type=manx + prefix = ""; + } else { + // deepObject style e.g. /pet?id[name]=cat&id[type]=manx + prefix = prefix + "["; + suffix = "]"; + containerSuffix = "]"; + containerPrefix = "["; + } + + StringJoiner joiner = new StringJoiner("&"); + + // add `optimizationMethodType` to the URL query string + if (getOptimizationMethodType() != null) { + joiner.add(String.format("%soptimizationMethodType%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getOptimizationMethodType()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + // add `optimizationParameter` to the URL query string + if (getOptimizationParameter() != null) { + for (int i = 0; i < getOptimizationParameter().size(); i++) { + if (getOptimizationParameter().get(i) != null) { + joiner.add(getOptimizationParameter().get(i).toUrlQueryString(String.format("%soptimizationParameter%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix)))); + } + } + } + + return joiner.toString(); + } +} + diff --git a/vcell-restclient/src/main/java/org/vcell/restclient/model/CopasiOptimizationMethodOptimizationMethodType.java b/vcell-restclient/src/main/java/org/vcell/restclient/model/CopasiOptimizationMethodOptimizationMethodType.java new file mode 100644 index 0000000000..09b8edc5cc --- /dev/null +++ b/vcell-restclient/src/main/java/org/vcell/restclient/model/CopasiOptimizationMethodOptimizationMethodType.java @@ -0,0 +1,100 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.StringJoiner; +import java.util.Objects; +import java.util.Map; +import java.util.HashMap; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Gets or Sets CopasiOptimizationMethodOptimizationMethodType + */ +public enum CopasiOptimizationMethodOptimizationMethodType { + + SRES("SRES"), + + EVOLUTIONARYPROGRAM("evolutionaryProgram"), + + GENETICALGORITHM("geneticAlgorithm"), + + GENETICALGORITHMSR("geneticAlgorithmSR"), + + HOOKEJEEVES("hookeJeeves"), + + LEVENBERGMARQUARDT("levenbergMarquardt"), + + NELDERMEAD("nelderMead"), + + PARTICLESWARM("particleSwarm"), + + PRAXIS("praxis"), + + RANDOMSEARCH("randomSearch"), + + SIMULATEDANNEALING("simulatedAnnealing"), + + STEEPESTDESCENT("steepestDescent"), + + TRUNCATEDNEWTON("truncatedNewton"); + + private String value; + + CopasiOptimizationMethodOptimizationMethodType(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + @JsonCreator + public static CopasiOptimizationMethodOptimizationMethodType fromValue(String value) { + for (CopasiOptimizationMethodOptimizationMethodType b : CopasiOptimizationMethodOptimizationMethodType.values()) { + if (b.value.equals(value)) { + return b; + } + } + throw new IllegalArgumentException("Unexpected value '" + value + "'"); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + if (prefix == null) { + prefix = ""; + } + + return String.format("%s=%s", prefix, this.toString()); + } + +} + diff --git a/vcell-restclient/src/main/java/org/vcell/restclient/model/CopasiOptimizationParameter.java b/vcell-restclient/src/main/java/org/vcell/restclient/model/CopasiOptimizationParameter.java new file mode 100644 index 0000000000..eba8c8c8f5 --- /dev/null +++ b/vcell-restclient/src/main/java/org/vcell/restclient/model/CopasiOptimizationParameter.java @@ -0,0 +1,224 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.StringJoiner; +import java.util.Objects; +import java.util.Map; +import java.util.HashMap; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Arrays; +import org.vcell.restclient.model.CopasiOptimizationParameterDataType; +import org.vcell.restclient.model.CopasiOptimizationParameterParamType; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + + +/** + * CopasiOptimizationParameter + */ +@JsonPropertyOrder({ + CopasiOptimizationParameter.JSON_PROPERTY_DATA_TYPE, + CopasiOptimizationParameter.JSON_PROPERTY_PARAM_TYPE, + CopasiOptimizationParameter.JSON_PROPERTY_VALUE +}) +@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen") +public class CopasiOptimizationParameter { + public static final String JSON_PROPERTY_DATA_TYPE = "dataType"; + private CopasiOptimizationParameterDataType dataType; + + public static final String JSON_PROPERTY_PARAM_TYPE = "paramType"; + private CopasiOptimizationParameterParamType paramType; + + public static final String JSON_PROPERTY_VALUE = "value"; + private Double value; + + public CopasiOptimizationParameter() { + } + + public CopasiOptimizationParameter dataType(CopasiOptimizationParameterDataType dataType) { + this.dataType = dataType; + return this; + } + + /** + * Get dataType + * @return dataType + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_DATA_TYPE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public CopasiOptimizationParameterDataType getDataType() { + return dataType; + } + + + @JsonProperty(JSON_PROPERTY_DATA_TYPE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setDataType(CopasiOptimizationParameterDataType dataType) { + this.dataType = dataType; + } + + + public CopasiOptimizationParameter paramType(CopasiOptimizationParameterParamType paramType) { + this.paramType = paramType; + return this; + } + + /** + * Get paramType + * @return paramType + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_PARAM_TYPE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public CopasiOptimizationParameterParamType getParamType() { + return paramType; + } + + + @JsonProperty(JSON_PROPERTY_PARAM_TYPE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setParamType(CopasiOptimizationParameterParamType paramType) { + this.paramType = paramType; + } + + + public CopasiOptimizationParameter value(Double value) { + this.value = value; + return this; + } + + /** + * Get value + * @return value + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_VALUE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public Double getValue() { + return value; + } + + + @JsonProperty(JSON_PROPERTY_VALUE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setValue(Double value) { + this.value = value; + } + + + /** + * Return true if this CopasiOptimizationParameter object is equal to o. + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CopasiOptimizationParameter copasiOptimizationParameter = (CopasiOptimizationParameter) o; + return Objects.equals(this.dataType, copasiOptimizationParameter.dataType) && + Objects.equals(this.paramType, copasiOptimizationParameter.paramType) && + Objects.equals(this.value, copasiOptimizationParameter.value); + } + + @Override + public int hashCode() { + return Objects.hash(dataType, paramType, value); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class CopasiOptimizationParameter {\n"); + sb.append(" dataType: ").append(toIndentedString(dataType)).append("\n"); + sb.append(" paramType: ").append(toIndentedString(paramType)).append("\n"); + sb.append(" value: ").append(toIndentedString(value)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + /** + * Convert the instance into URL query string. + * + * @return URL query string + */ + public String toUrlQueryString() { + return toUrlQueryString(null); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + String suffix = ""; + String containerSuffix = ""; + String containerPrefix = ""; + if (prefix == null) { + // style=form, explode=true, e.g. /pet?name=cat&type=manx + prefix = ""; + } else { + // deepObject style e.g. /pet?id[name]=cat&id[type]=manx + prefix = prefix + "["; + suffix = "]"; + containerSuffix = "]"; + containerPrefix = "["; + } + + StringJoiner joiner = new StringJoiner("&"); + + // add `dataType` to the URL query string + if (getDataType() != null) { + joiner.add(String.format("%sdataType%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getDataType()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + // add `paramType` to the URL query string + if (getParamType() != null) { + joiner.add(String.format("%sparamType%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getParamType()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + // add `value` to the URL query string + if (getValue() != null) { + joiner.add(String.format("%svalue%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getValue()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + return joiner.toString(); + } +} + diff --git a/vcell-restclient/src/main/java/org/vcell/restclient/model/CopasiOptimizationParameterDataType.java b/vcell-restclient/src/main/java/org/vcell/restclient/model/CopasiOptimizationParameterDataType.java new file mode 100644 index 0000000000..7f385c42a1 --- /dev/null +++ b/vcell-restclient/src/main/java/org/vcell/restclient/model/CopasiOptimizationParameterDataType.java @@ -0,0 +1,78 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.StringJoiner; +import java.util.Objects; +import java.util.Map; +import java.util.HashMap; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Gets or Sets CopasiOptimizationParameterDataType + */ +public enum CopasiOptimizationParameterDataType { + + DOUBLE("double"), + + INT("int"); + + private String value; + + CopasiOptimizationParameterDataType(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + @JsonCreator + public static CopasiOptimizationParameterDataType fromValue(String value) { + for (CopasiOptimizationParameterDataType b : CopasiOptimizationParameterDataType.values()) { + if (b.value.equals(value)) { + return b; + } + } + throw new IllegalArgumentException("Unexpected value '" + value + "'"); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + if (prefix == null) { + prefix = ""; + } + + return String.format("%s=%s", prefix, this.toString()); + } + +} + diff --git a/vcell-restclient/src/main/java/org/vcell/restclient/model/CopasiOptimizationParameterParamType.java b/vcell-restclient/src/main/java/org/vcell/restclient/model/CopasiOptimizationParameterParamType.java new file mode 100644 index 0000000000..c103846a52 --- /dev/null +++ b/vcell-restclient/src/main/java/org/vcell/restclient/model/CopasiOptimizationParameterParamType.java @@ -0,0 +1,102 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.StringJoiner; +import java.util.Objects; +import java.util.Map; +import java.util.HashMap; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Gets or Sets CopasiOptimizationParameterParamType + */ +public enum CopasiOptimizationParameterParamType { + + COOLINGFACTOR("coolingFactor"), + + ITERATIONLIMIT("iterationLimit"), + + NUMBEROFGENERATIONS("numberOfGenerations"), + + NUMBEROFITERATIONS("numberOfIterations"), + + PF("pf"), + + POPULATIONSIZE("populationSize"), + + RANDOMNUMBERGENERATOR("randomNumberGenerator"), + + RHO("rho"), + + SCALE("scale"), + + SEED("seed"), + + STARTTEMPERATURE("startTemperature"), + + STDDEVIATION("stdDeviation"), + + SWARMSIZE("swarmSize"), + + TOLERANCE("tolerance"); + + private String value; + + CopasiOptimizationParameterParamType(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + @JsonCreator + public static CopasiOptimizationParameterParamType fromValue(String value) { + for (CopasiOptimizationParameterParamType b : CopasiOptimizationParameterParamType.values()) { + if (b.value.equals(value)) { + return b; + } + } + throw new IllegalArgumentException("Unexpected value '" + value + "'"); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + if (prefix == null) { + prefix = ""; + } + + return String.format("%s=%s", prefix, this.toString()); + } + +} + diff --git a/vcell-restclient/src/main/java/org/vcell/restclient/model/OptJobStatus.java b/vcell-restclient/src/main/java/org/vcell/restclient/model/OptJobStatus.java new file mode 100644 index 0000000000..dbb0777d05 --- /dev/null +++ b/vcell-restclient/src/main/java/org/vcell/restclient/model/OptJobStatus.java @@ -0,0 +1,86 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.StringJoiner; +import java.util.Objects; +import java.util.Map; +import java.util.HashMap; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Gets or Sets OptJobStatus + */ +public enum OptJobStatus { + + SUBMITTED("SUBMITTED"), + + QUEUED("QUEUED"), + + RUNNING("RUNNING"), + + COMPLETE("COMPLETE"), + + FAILED("FAILED"), + + STOPPED("STOPPED"); + + private String value; + + OptJobStatus(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + @JsonCreator + public static OptJobStatus fromValue(String value) { + for (OptJobStatus b : OptJobStatus.values()) { + if (b.value.equals(value)) { + return b; + } + } + throw new IllegalArgumentException("Unexpected value '" + value + "'"); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + if (prefix == null) { + prefix = ""; + } + + return String.format("%s=%s", prefix, this.toString()); + } + +} + diff --git a/vcell-restclient/src/main/java/org/vcell/restclient/model/OptProblem.java b/vcell-restclient/src/main/java/org/vcell/restclient/model/OptProblem.java new file mode 100644 index 0000000000..4a2f9d1f83 --- /dev/null +++ b/vcell-restclient/src/main/java/org/vcell/restclient/model/OptProblem.java @@ -0,0 +1,373 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.StringJoiner; +import java.util.Objects; +import java.util.Map; +import java.util.HashMap; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.vcell.restclient.model.CopasiOptimizationMethod; +import org.vcell.restclient.model.ParameterDescription; +import org.vcell.restclient.model.ReferenceVariable; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + + +/** + * OptProblem + */ +@JsonPropertyOrder({ + OptProblem.JSON_PROPERTY_COPASI_OPTIMIZATION_METHOD, + OptProblem.JSON_PROPERTY_DATA_SET, + OptProblem.JSON_PROPERTY_MATH_MODEL_SBML_CONTENTS, + OptProblem.JSON_PROPERTY_NUMBER_OF_OPTIMIZATION_RUNS, + OptProblem.JSON_PROPERTY_PARAMETER_DESCRIPTION_LIST, + OptProblem.JSON_PROPERTY_REFERENCE_VARIABLE +}) +@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen") +public class OptProblem { + public static final String JSON_PROPERTY_COPASI_OPTIMIZATION_METHOD = "copasiOptimizationMethod"; + private CopasiOptimizationMethod copasiOptimizationMethod; + + public static final String JSON_PROPERTY_DATA_SET = "dataSet"; + private List> dataSet; + + public static final String JSON_PROPERTY_MATH_MODEL_SBML_CONTENTS = "mathModelSbmlContents"; + private String mathModelSbmlContents; + + public static final String JSON_PROPERTY_NUMBER_OF_OPTIMIZATION_RUNS = "numberOfOptimizationRuns"; + private Integer numberOfOptimizationRuns; + + public static final String JSON_PROPERTY_PARAMETER_DESCRIPTION_LIST = "parameterDescriptionList"; + private List parameterDescriptionList; + + public static final String JSON_PROPERTY_REFERENCE_VARIABLE = "referenceVariable"; + private List referenceVariable; + + public OptProblem() { + } + + public OptProblem copasiOptimizationMethod(CopasiOptimizationMethod copasiOptimizationMethod) { + this.copasiOptimizationMethod = copasiOptimizationMethod; + return this; + } + + /** + * Get copasiOptimizationMethod + * @return copasiOptimizationMethod + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_COPASI_OPTIMIZATION_METHOD) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public CopasiOptimizationMethod getCopasiOptimizationMethod() { + return copasiOptimizationMethod; + } + + + @JsonProperty(JSON_PROPERTY_COPASI_OPTIMIZATION_METHOD) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setCopasiOptimizationMethod(CopasiOptimizationMethod copasiOptimizationMethod) { + this.copasiOptimizationMethod = copasiOptimizationMethod; + } + + + public OptProblem dataSet(List> dataSet) { + this.dataSet = dataSet; + return this; + } + + public OptProblem addDataSetItem(List dataSetItem) { + if (this.dataSet == null) { + this.dataSet = new ArrayList<>(); + } + this.dataSet.add(dataSetItem); + return this; + } + + /** + * Get dataSet + * @return dataSet + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_DATA_SET) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public List> getDataSet() { + return dataSet; + } + + + @JsonProperty(JSON_PROPERTY_DATA_SET) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setDataSet(List> dataSet) { + this.dataSet = dataSet; + } + + + public OptProblem mathModelSbmlContents(String mathModelSbmlContents) { + this.mathModelSbmlContents = mathModelSbmlContents; + return this; + } + + /** + * Get mathModelSbmlContents + * @return mathModelSbmlContents + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_MATH_MODEL_SBML_CONTENTS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public String getMathModelSbmlContents() { + return mathModelSbmlContents; + } + + + @JsonProperty(JSON_PROPERTY_MATH_MODEL_SBML_CONTENTS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setMathModelSbmlContents(String mathModelSbmlContents) { + this.mathModelSbmlContents = mathModelSbmlContents; + } + + + public OptProblem numberOfOptimizationRuns(Integer numberOfOptimizationRuns) { + this.numberOfOptimizationRuns = numberOfOptimizationRuns; + return this; + } + + /** + * Get numberOfOptimizationRuns + * @return numberOfOptimizationRuns + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_NUMBER_OF_OPTIMIZATION_RUNS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public Integer getNumberOfOptimizationRuns() { + return numberOfOptimizationRuns; + } + + + @JsonProperty(JSON_PROPERTY_NUMBER_OF_OPTIMIZATION_RUNS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setNumberOfOptimizationRuns(Integer numberOfOptimizationRuns) { + this.numberOfOptimizationRuns = numberOfOptimizationRuns; + } + + + public OptProblem parameterDescriptionList(List parameterDescriptionList) { + this.parameterDescriptionList = parameterDescriptionList; + return this; + } + + public OptProblem addParameterDescriptionListItem(ParameterDescription parameterDescriptionListItem) { + if (this.parameterDescriptionList == null) { + this.parameterDescriptionList = new ArrayList<>(); + } + this.parameterDescriptionList.add(parameterDescriptionListItem); + return this; + } + + /** + * Get parameterDescriptionList + * @return parameterDescriptionList + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_PARAMETER_DESCRIPTION_LIST) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public List getParameterDescriptionList() { + return parameterDescriptionList; + } + + + @JsonProperty(JSON_PROPERTY_PARAMETER_DESCRIPTION_LIST) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setParameterDescriptionList(List parameterDescriptionList) { + this.parameterDescriptionList = parameterDescriptionList; + } + + + public OptProblem referenceVariable(List referenceVariable) { + this.referenceVariable = referenceVariable; + return this; + } + + public OptProblem addReferenceVariableItem(ReferenceVariable referenceVariableItem) { + if (this.referenceVariable == null) { + this.referenceVariable = new ArrayList<>(); + } + this.referenceVariable.add(referenceVariableItem); + return this; + } + + /** + * Get referenceVariable + * @return referenceVariable + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_REFERENCE_VARIABLE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public List getReferenceVariable() { + return referenceVariable; + } + + + @JsonProperty(JSON_PROPERTY_REFERENCE_VARIABLE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setReferenceVariable(List referenceVariable) { + this.referenceVariable = referenceVariable; + } + + + /** + * Return true if this OptProblem object is equal to o. + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + OptProblem optProblem = (OptProblem) o; + return Objects.equals(this.copasiOptimizationMethod, optProblem.copasiOptimizationMethod) && + Objects.equals(this.dataSet, optProblem.dataSet) && + Objects.equals(this.mathModelSbmlContents, optProblem.mathModelSbmlContents) && + Objects.equals(this.numberOfOptimizationRuns, optProblem.numberOfOptimizationRuns) && + Objects.equals(this.parameterDescriptionList, optProblem.parameterDescriptionList) && + Objects.equals(this.referenceVariable, optProblem.referenceVariable); + } + + @Override + public int hashCode() { + return Objects.hash(copasiOptimizationMethod, dataSet, mathModelSbmlContents, numberOfOptimizationRuns, parameterDescriptionList, referenceVariable); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class OptProblem {\n"); + sb.append(" copasiOptimizationMethod: ").append(toIndentedString(copasiOptimizationMethod)).append("\n"); + sb.append(" dataSet: ").append(toIndentedString(dataSet)).append("\n"); + sb.append(" mathModelSbmlContents: ").append(toIndentedString(mathModelSbmlContents)).append("\n"); + sb.append(" numberOfOptimizationRuns: ").append(toIndentedString(numberOfOptimizationRuns)).append("\n"); + sb.append(" parameterDescriptionList: ").append(toIndentedString(parameterDescriptionList)).append("\n"); + sb.append(" referenceVariable: ").append(toIndentedString(referenceVariable)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + /** + * Convert the instance into URL query string. + * + * @return URL query string + */ + public String toUrlQueryString() { + return toUrlQueryString(null); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + String suffix = ""; + String containerSuffix = ""; + String containerPrefix = ""; + if (prefix == null) { + // style=form, explode=true, e.g. /pet?name=cat&type=manx + prefix = ""; + } else { + // deepObject style e.g. /pet?id[name]=cat&id[type]=manx + prefix = prefix + "["; + suffix = "]"; + containerSuffix = "]"; + containerPrefix = "["; + } + + StringJoiner joiner = new StringJoiner("&"); + + // add `copasiOptimizationMethod` to the URL query string + if (getCopasiOptimizationMethod() != null) { + joiner.add(getCopasiOptimizationMethod().toUrlQueryString(prefix + "copasiOptimizationMethod" + suffix)); + } + + // add `dataSet` to the URL query string + if (getDataSet() != null) { + for (int i = 0; i < getDataSet().size(); i++) { + joiner.add(String.format("%sdataSet%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), + URLEncoder.encode(String.valueOf(getDataSet().get(i)), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + } + + // add `mathModelSbmlContents` to the URL query string + if (getMathModelSbmlContents() != null) { + joiner.add(String.format("%smathModelSbmlContents%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getMathModelSbmlContents()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + // add `numberOfOptimizationRuns` to the URL query string + if (getNumberOfOptimizationRuns() != null) { + joiner.add(String.format("%snumberOfOptimizationRuns%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getNumberOfOptimizationRuns()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + // add `parameterDescriptionList` to the URL query string + if (getParameterDescriptionList() != null) { + for (int i = 0; i < getParameterDescriptionList().size(); i++) { + if (getParameterDescriptionList().get(i) != null) { + joiner.add(getParameterDescriptionList().get(i).toUrlQueryString(String.format("%sparameterDescriptionList%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix)))); + } + } + } + + // add `referenceVariable` to the URL query string + if (getReferenceVariable() != null) { + for (int i = 0; i < getReferenceVariable().size(); i++) { + if (getReferenceVariable().get(i) != null) { + joiner.add(getReferenceVariable().get(i).toUrlQueryString(String.format("%sreferenceVariable%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix)))); + } + } + } + + return joiner.toString(); + } +} + diff --git a/vcell-restclient/src/main/java/org/vcell/restclient/model/OptProgressItem.java b/vcell-restclient/src/main/java/org/vcell/restclient/model/OptProgressItem.java new file mode 100644 index 0000000000..441e34e8be --- /dev/null +++ b/vcell-restclient/src/main/java/org/vcell/restclient/model/OptProgressItem.java @@ -0,0 +1,186 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.StringJoiner; +import java.util.Objects; +import java.util.Map; +import java.util.HashMap; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Arrays; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + + +/** + * OptProgressItem + */ +@JsonPropertyOrder({ + OptProgressItem.JSON_PROPERTY_NUM_FUNCTION_EVALUATIONS, + OptProgressItem.JSON_PROPERTY_OBJ_FUNC_VALUE +}) +@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen") +public class OptProgressItem { + public static final String JSON_PROPERTY_NUM_FUNCTION_EVALUATIONS = "numFunctionEvaluations"; + private Integer numFunctionEvaluations; + + public static final String JSON_PROPERTY_OBJ_FUNC_VALUE = "objFuncValue"; + private Double objFuncValue; + + public OptProgressItem() { + } + + public OptProgressItem numFunctionEvaluations(Integer numFunctionEvaluations) { + this.numFunctionEvaluations = numFunctionEvaluations; + return this; + } + + /** + * Get numFunctionEvaluations + * @return numFunctionEvaluations + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_NUM_FUNCTION_EVALUATIONS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public Integer getNumFunctionEvaluations() { + return numFunctionEvaluations; + } + + + @JsonProperty(JSON_PROPERTY_NUM_FUNCTION_EVALUATIONS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setNumFunctionEvaluations(Integer numFunctionEvaluations) { + this.numFunctionEvaluations = numFunctionEvaluations; + } + + + public OptProgressItem objFuncValue(Double objFuncValue) { + this.objFuncValue = objFuncValue; + return this; + } + + /** + * Get objFuncValue + * @return objFuncValue + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_OBJ_FUNC_VALUE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public Double getObjFuncValue() { + return objFuncValue; + } + + + @JsonProperty(JSON_PROPERTY_OBJ_FUNC_VALUE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setObjFuncValue(Double objFuncValue) { + this.objFuncValue = objFuncValue; + } + + + /** + * Return true if this OptProgressItem object is equal to o. + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + OptProgressItem optProgressItem = (OptProgressItem) o; + return Objects.equals(this.numFunctionEvaluations, optProgressItem.numFunctionEvaluations) && + Objects.equals(this.objFuncValue, optProgressItem.objFuncValue); + } + + @Override + public int hashCode() { + return Objects.hash(numFunctionEvaluations, objFuncValue); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class OptProgressItem {\n"); + sb.append(" numFunctionEvaluations: ").append(toIndentedString(numFunctionEvaluations)).append("\n"); + sb.append(" objFuncValue: ").append(toIndentedString(objFuncValue)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + /** + * Convert the instance into URL query string. + * + * @return URL query string + */ + public String toUrlQueryString() { + return toUrlQueryString(null); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + String suffix = ""; + String containerSuffix = ""; + String containerPrefix = ""; + if (prefix == null) { + // style=form, explode=true, e.g. /pet?name=cat&type=manx + prefix = ""; + } else { + // deepObject style e.g. /pet?id[name]=cat&id[type]=manx + prefix = prefix + "["; + suffix = "]"; + containerSuffix = "]"; + containerPrefix = "["; + } + + StringJoiner joiner = new StringJoiner("&"); + + // add `numFunctionEvaluations` to the URL query string + if (getNumFunctionEvaluations() != null) { + joiner.add(String.format("%snumFunctionEvaluations%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getNumFunctionEvaluations()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + // add `objFuncValue` to the URL query string + if (getObjFuncValue() != null) { + joiner.add(String.format("%sobjFuncValue%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getObjFuncValue()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + return joiner.toString(); + } +} + diff --git a/vcell-restclient/src/main/java/org/vcell/restclient/model/OptProgressReport.java b/vcell-restclient/src/main/java/org/vcell/restclient/model/OptProgressReport.java new file mode 100644 index 0000000000..9911a97fd5 --- /dev/null +++ b/vcell-restclient/src/main/java/org/vcell/restclient/model/OptProgressReport.java @@ -0,0 +1,216 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.StringJoiner; +import java.util.Objects; +import java.util.Map; +import java.util.HashMap; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.vcell.restclient.model.OptProgressItem; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + + +/** + * OptProgressReport + */ +@JsonPropertyOrder({ + OptProgressReport.JSON_PROPERTY_BEST_PARAM_VALUES, + OptProgressReport.JSON_PROPERTY_PROGRESS_ITEMS +}) +@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen") +public class OptProgressReport { + public static final String JSON_PROPERTY_BEST_PARAM_VALUES = "bestParamValues"; + private Map bestParamValues = new HashMap<>(); + + public static final String JSON_PROPERTY_PROGRESS_ITEMS = "progressItems"; + private List progressItems; + + public OptProgressReport() { + } + + public OptProgressReport bestParamValues(Map bestParamValues) { + this.bestParamValues = bestParamValues; + return this; + } + + public OptProgressReport putBestParamValuesItem(String key, Double bestParamValuesItem) { + if (this.bestParamValues == null) { + this.bestParamValues = new HashMap<>(); + } + this.bestParamValues.put(key, bestParamValuesItem); + return this; + } + + /** + * Get bestParamValues + * @return bestParamValues + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_BEST_PARAM_VALUES) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public Map getBestParamValues() { + return bestParamValues; + } + + + @JsonProperty(JSON_PROPERTY_BEST_PARAM_VALUES) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setBestParamValues(Map bestParamValues) { + this.bestParamValues = bestParamValues; + } + + + public OptProgressReport progressItems(List progressItems) { + this.progressItems = progressItems; + return this; + } + + public OptProgressReport addProgressItemsItem(OptProgressItem progressItemsItem) { + if (this.progressItems == null) { + this.progressItems = new ArrayList<>(); + } + this.progressItems.add(progressItemsItem); + return this; + } + + /** + * Get progressItems + * @return progressItems + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_PROGRESS_ITEMS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public List getProgressItems() { + return progressItems; + } + + + @JsonProperty(JSON_PROPERTY_PROGRESS_ITEMS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setProgressItems(List progressItems) { + this.progressItems = progressItems; + } + + + /** + * Return true if this OptProgressReport object is equal to o. + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + OptProgressReport optProgressReport = (OptProgressReport) o; + return Objects.equals(this.bestParamValues, optProgressReport.bestParamValues) && + Objects.equals(this.progressItems, optProgressReport.progressItems); + } + + @Override + public int hashCode() { + return Objects.hash(bestParamValues, progressItems); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class OptProgressReport {\n"); + sb.append(" bestParamValues: ").append(toIndentedString(bestParamValues)).append("\n"); + sb.append(" progressItems: ").append(toIndentedString(progressItems)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + /** + * Convert the instance into URL query string. + * + * @return URL query string + */ + public String toUrlQueryString() { + return toUrlQueryString(null); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + String suffix = ""; + String containerSuffix = ""; + String containerPrefix = ""; + if (prefix == null) { + // style=form, explode=true, e.g. /pet?name=cat&type=manx + prefix = ""; + } else { + // deepObject style e.g. /pet?id[name]=cat&id[type]=manx + prefix = prefix + "["; + suffix = "]"; + containerSuffix = "]"; + containerPrefix = "["; + } + + StringJoiner joiner = new StringJoiner("&"); + + // add `bestParamValues` to the URL query string + if (getBestParamValues() != null) { + for (String _key : getBestParamValues().keySet()) { + joiner.add(String.format("%sbestParamValues%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, _key, containerSuffix), + getBestParamValues().get(_key), URLEncoder.encode(String.valueOf(getBestParamValues().get(_key)), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + } + + // add `progressItems` to the URL query string + if (getProgressItems() != null) { + for (int i = 0; i < getProgressItems().size(); i++) { + if (getProgressItems().get(i) != null) { + joiner.add(getProgressItems().get(i).toUrlQueryString(String.format("%sprogressItems%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix)))); + } + } + } + + return joiner.toString(); + } +} + diff --git a/vcell-restclient/src/main/java/org/vcell/restclient/model/OptResultSet.java b/vcell-restclient/src/main/java/org/vcell/restclient/model/OptResultSet.java new file mode 100644 index 0000000000..6f94a7d649 --- /dev/null +++ b/vcell-restclient/src/main/java/org/vcell/restclient/model/OptResultSet.java @@ -0,0 +1,273 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.StringJoiner; +import java.util.Objects; +import java.util.Map; +import java.util.HashMap; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import org.vcell.restclient.model.OptProgressReport; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + + +/** + * OptResultSet + */ +@JsonPropertyOrder({ + OptResultSet.JSON_PROPERTY_NUM_FUNCTION_EVALUATIONS, + OptResultSet.JSON_PROPERTY_OBJECTIVE_FUNCTION, + OptResultSet.JSON_PROPERTY_OPT_PARAMETER_VALUES, + OptResultSet.JSON_PROPERTY_OPT_PROGRESS_REPORT +}) +@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen") +public class OptResultSet { + public static final String JSON_PROPERTY_NUM_FUNCTION_EVALUATIONS = "numFunctionEvaluations"; + private Integer numFunctionEvaluations; + + public static final String JSON_PROPERTY_OBJECTIVE_FUNCTION = "objectiveFunction"; + private Double objectiveFunction; + + public static final String JSON_PROPERTY_OPT_PARAMETER_VALUES = "optParameterValues"; + private Map optParameterValues = new HashMap<>(); + + public static final String JSON_PROPERTY_OPT_PROGRESS_REPORT = "optProgressReport"; + private OptProgressReport optProgressReport; + + public OptResultSet() { + } + + public OptResultSet numFunctionEvaluations(Integer numFunctionEvaluations) { + this.numFunctionEvaluations = numFunctionEvaluations; + return this; + } + + /** + * Get numFunctionEvaluations + * @return numFunctionEvaluations + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_NUM_FUNCTION_EVALUATIONS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public Integer getNumFunctionEvaluations() { + return numFunctionEvaluations; + } + + + @JsonProperty(JSON_PROPERTY_NUM_FUNCTION_EVALUATIONS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setNumFunctionEvaluations(Integer numFunctionEvaluations) { + this.numFunctionEvaluations = numFunctionEvaluations; + } + + + public OptResultSet objectiveFunction(Double objectiveFunction) { + this.objectiveFunction = objectiveFunction; + return this; + } + + /** + * Get objectiveFunction + * @return objectiveFunction + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_OBJECTIVE_FUNCTION) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public Double getObjectiveFunction() { + return objectiveFunction; + } + + + @JsonProperty(JSON_PROPERTY_OBJECTIVE_FUNCTION) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setObjectiveFunction(Double objectiveFunction) { + this.objectiveFunction = objectiveFunction; + } + + + public OptResultSet optParameterValues(Map optParameterValues) { + this.optParameterValues = optParameterValues; + return this; + } + + public OptResultSet putOptParameterValuesItem(String key, Double optParameterValuesItem) { + if (this.optParameterValues == null) { + this.optParameterValues = new HashMap<>(); + } + this.optParameterValues.put(key, optParameterValuesItem); + return this; + } + + /** + * Get optParameterValues + * @return optParameterValues + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_OPT_PARAMETER_VALUES) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public Map getOptParameterValues() { + return optParameterValues; + } + + + @JsonProperty(JSON_PROPERTY_OPT_PARAMETER_VALUES) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setOptParameterValues(Map optParameterValues) { + this.optParameterValues = optParameterValues; + } + + + public OptResultSet optProgressReport(OptProgressReport optProgressReport) { + this.optProgressReport = optProgressReport; + return this; + } + + /** + * Get optProgressReport + * @return optProgressReport + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_OPT_PROGRESS_REPORT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public OptProgressReport getOptProgressReport() { + return optProgressReport; + } + + + @JsonProperty(JSON_PROPERTY_OPT_PROGRESS_REPORT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setOptProgressReport(OptProgressReport optProgressReport) { + this.optProgressReport = optProgressReport; + } + + + /** + * Return true if this OptResultSet object is equal to o. + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + OptResultSet optResultSet = (OptResultSet) o; + return Objects.equals(this.numFunctionEvaluations, optResultSet.numFunctionEvaluations) && + Objects.equals(this.objectiveFunction, optResultSet.objectiveFunction) && + Objects.equals(this.optParameterValues, optResultSet.optParameterValues) && + Objects.equals(this.optProgressReport, optResultSet.optProgressReport); + } + + @Override + public int hashCode() { + return Objects.hash(numFunctionEvaluations, objectiveFunction, optParameterValues, optProgressReport); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class OptResultSet {\n"); + sb.append(" numFunctionEvaluations: ").append(toIndentedString(numFunctionEvaluations)).append("\n"); + sb.append(" objectiveFunction: ").append(toIndentedString(objectiveFunction)).append("\n"); + sb.append(" optParameterValues: ").append(toIndentedString(optParameterValues)).append("\n"); + sb.append(" optProgressReport: ").append(toIndentedString(optProgressReport)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + /** + * Convert the instance into URL query string. + * + * @return URL query string + */ + public String toUrlQueryString() { + return toUrlQueryString(null); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + String suffix = ""; + String containerSuffix = ""; + String containerPrefix = ""; + if (prefix == null) { + // style=form, explode=true, e.g. /pet?name=cat&type=manx + prefix = ""; + } else { + // deepObject style e.g. /pet?id[name]=cat&id[type]=manx + prefix = prefix + "["; + suffix = "]"; + containerSuffix = "]"; + containerPrefix = "["; + } + + StringJoiner joiner = new StringJoiner("&"); + + // add `numFunctionEvaluations` to the URL query string + if (getNumFunctionEvaluations() != null) { + joiner.add(String.format("%snumFunctionEvaluations%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getNumFunctionEvaluations()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + // add `objectiveFunction` to the URL query string + if (getObjectiveFunction() != null) { + joiner.add(String.format("%sobjectiveFunction%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getObjectiveFunction()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + // add `optParameterValues` to the URL query string + if (getOptParameterValues() != null) { + for (String _key : getOptParameterValues().keySet()) { + joiner.add(String.format("%soptParameterValues%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, _key, containerSuffix), + getOptParameterValues().get(_key), URLEncoder.encode(String.valueOf(getOptParameterValues().get(_key)), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + } + + // add `optProgressReport` to the URL query string + if (getOptProgressReport() != null) { + joiner.add(getOptProgressReport().toUrlQueryString(prefix + "optProgressReport" + suffix)); + } + + return joiner.toString(); + } +} + diff --git a/vcell-restclient/src/main/java/org/vcell/restclient/model/OptimizationJobStatus.java b/vcell-restclient/src/main/java/org/vcell/restclient/model/OptimizationJobStatus.java new file mode 100644 index 0000000000..18755748bc --- /dev/null +++ b/vcell-restclient/src/main/java/org/vcell/restclient/model/OptimizationJobStatus.java @@ -0,0 +1,333 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.StringJoiner; +import java.util.Objects; +import java.util.Map; +import java.util.HashMap; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Arrays; +import org.vcell.restclient.model.OptJobStatus; +import org.vcell.restclient.model.OptProgressReport; +import org.vcell.restclient.model.Vcellopt; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + + +/** + * OptimizationJobStatus + */ +@JsonPropertyOrder({ + OptimizationJobStatus.JSON_PROPERTY_ID, + OptimizationJobStatus.JSON_PROPERTY_STATUS, + OptimizationJobStatus.JSON_PROPERTY_STATUS_MESSAGE, + OptimizationJobStatus.JSON_PROPERTY_HTC_JOB_ID, + OptimizationJobStatus.JSON_PROPERTY_PROGRESS_REPORT, + OptimizationJobStatus.JSON_PROPERTY_RESULTS +}) +@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen") +public class OptimizationJobStatus { + public static final String JSON_PROPERTY_ID = "id"; + private String id; + + public static final String JSON_PROPERTY_STATUS = "status"; + private OptJobStatus status; + + public static final String JSON_PROPERTY_STATUS_MESSAGE = "statusMessage"; + private String statusMessage; + + public static final String JSON_PROPERTY_HTC_JOB_ID = "htcJobId"; + private String htcJobId; + + public static final String JSON_PROPERTY_PROGRESS_REPORT = "progressReport"; + private OptProgressReport progressReport; + + public static final String JSON_PROPERTY_RESULTS = "results"; + private Vcellopt results; + + public OptimizationJobStatus() { + } + + public OptimizationJobStatus id(String id) { + this.id = id; + return this; + } + + /** + * Get id + * @return id + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_ID) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public String getId() { + return id; + } + + + @JsonProperty(JSON_PROPERTY_ID) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setId(String id) { + this.id = id; + } + + + public OptimizationJobStatus status(OptJobStatus status) { + this.status = status; + return this; + } + + /** + * Get status + * @return status + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_STATUS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public OptJobStatus getStatus() { + return status; + } + + + @JsonProperty(JSON_PROPERTY_STATUS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setStatus(OptJobStatus status) { + this.status = status; + } + + + public OptimizationJobStatus statusMessage(String statusMessage) { + this.statusMessage = statusMessage; + return this; + } + + /** + * Get statusMessage + * @return statusMessage + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_STATUS_MESSAGE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public String getStatusMessage() { + return statusMessage; + } + + + @JsonProperty(JSON_PROPERTY_STATUS_MESSAGE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setStatusMessage(String statusMessage) { + this.statusMessage = statusMessage; + } + + + public OptimizationJobStatus htcJobId(String htcJobId) { + this.htcJobId = htcJobId; + return this; + } + + /** + * Get htcJobId + * @return htcJobId + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_HTC_JOB_ID) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public String getHtcJobId() { + return htcJobId; + } + + + @JsonProperty(JSON_PROPERTY_HTC_JOB_ID) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setHtcJobId(String htcJobId) { + this.htcJobId = htcJobId; + } + + + public OptimizationJobStatus progressReport(OptProgressReport progressReport) { + this.progressReport = progressReport; + return this; + } + + /** + * Get progressReport + * @return progressReport + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_PROGRESS_REPORT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public OptProgressReport getProgressReport() { + return progressReport; + } + + + @JsonProperty(JSON_PROPERTY_PROGRESS_REPORT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setProgressReport(OptProgressReport progressReport) { + this.progressReport = progressReport; + } + + + public OptimizationJobStatus results(Vcellopt results) { + this.results = results; + return this; + } + + /** + * Get results + * @return results + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_RESULTS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public Vcellopt getResults() { + return results; + } + + + @JsonProperty(JSON_PROPERTY_RESULTS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setResults(Vcellopt results) { + this.results = results; + } + + + /** + * Return true if this OptimizationJobStatus object is equal to o. + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + OptimizationJobStatus optimizationJobStatus = (OptimizationJobStatus) o; + return Objects.equals(this.id, optimizationJobStatus.id) && + Objects.equals(this.status, optimizationJobStatus.status) && + Objects.equals(this.statusMessage, optimizationJobStatus.statusMessage) && + Objects.equals(this.htcJobId, optimizationJobStatus.htcJobId) && + Objects.equals(this.progressReport, optimizationJobStatus.progressReport) && + Objects.equals(this.results, optimizationJobStatus.results); + } + + @Override + public int hashCode() { + return Objects.hash(id, status, statusMessage, htcJobId, progressReport, results); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class OptimizationJobStatus {\n"); + sb.append(" id: ").append(toIndentedString(id)).append("\n"); + sb.append(" status: ").append(toIndentedString(status)).append("\n"); + sb.append(" statusMessage: ").append(toIndentedString(statusMessage)).append("\n"); + sb.append(" htcJobId: ").append(toIndentedString(htcJobId)).append("\n"); + sb.append(" progressReport: ").append(toIndentedString(progressReport)).append("\n"); + sb.append(" results: ").append(toIndentedString(results)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + /** + * Convert the instance into URL query string. + * + * @return URL query string + */ + public String toUrlQueryString() { + return toUrlQueryString(null); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + String suffix = ""; + String containerSuffix = ""; + String containerPrefix = ""; + if (prefix == null) { + // style=form, explode=true, e.g. /pet?name=cat&type=manx + prefix = ""; + } else { + // deepObject style e.g. /pet?id[name]=cat&id[type]=manx + prefix = prefix + "["; + suffix = "]"; + containerSuffix = "]"; + containerPrefix = "["; + } + + StringJoiner joiner = new StringJoiner("&"); + + // add `id` to the URL query string + if (getId() != null) { + joiner.add(String.format("%sid%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getId()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + // add `status` to the URL query string + if (getStatus() != null) { + joiner.add(String.format("%sstatus%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getStatus()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + // add `statusMessage` to the URL query string + if (getStatusMessage() != null) { + joiner.add(String.format("%sstatusMessage%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getStatusMessage()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + // add `htcJobId` to the URL query string + if (getHtcJobId() != null) { + joiner.add(String.format("%shtcJobId%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getHtcJobId()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + // add `progressReport` to the URL query string + if (getProgressReport() != null) { + joiner.add(getProgressReport().toUrlQueryString(prefix + "progressReport" + suffix)); + } + + // add `results` to the URL query string + if (getResults() != null) { + joiner.add(getResults().toUrlQueryString(prefix + "results" + suffix)); + } + + return joiner.toString(); + } +} + diff --git a/vcell-restclient/src/main/java/org/vcell/restclient/model/ParameterDescription.java b/vcell-restclient/src/main/java/org/vcell/restclient/model/ParameterDescription.java new file mode 100644 index 0000000000..bd4862d263 --- /dev/null +++ b/vcell-restclient/src/main/java/org/vcell/restclient/model/ParameterDescription.java @@ -0,0 +1,294 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.StringJoiner; +import java.util.Objects; +import java.util.Map; +import java.util.HashMap; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Arrays; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + + +/** + * ParameterDescription + */ +@JsonPropertyOrder({ + ParameterDescription.JSON_PROPERTY_INITIAL_VALUE, + ParameterDescription.JSON_PROPERTY_MAX_VALUE, + ParameterDescription.JSON_PROPERTY_MIN_VALUE, + ParameterDescription.JSON_PROPERTY_NAME, + ParameterDescription.JSON_PROPERTY_SCALE +}) +@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen") +public class ParameterDescription { + public static final String JSON_PROPERTY_INITIAL_VALUE = "initialValue"; + private Double initialValue; + + public static final String JSON_PROPERTY_MAX_VALUE = "maxValue"; + private Double maxValue; + + public static final String JSON_PROPERTY_MIN_VALUE = "minValue"; + private Double minValue; + + public static final String JSON_PROPERTY_NAME = "name"; + private String name; + + public static final String JSON_PROPERTY_SCALE = "scale"; + private Double scale; + + public ParameterDescription() { + } + + public ParameterDescription initialValue(Double initialValue) { + this.initialValue = initialValue; + return this; + } + + /** + * Get initialValue + * @return initialValue + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_INITIAL_VALUE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public Double getInitialValue() { + return initialValue; + } + + + @JsonProperty(JSON_PROPERTY_INITIAL_VALUE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setInitialValue(Double initialValue) { + this.initialValue = initialValue; + } + + + public ParameterDescription maxValue(Double maxValue) { + this.maxValue = maxValue; + return this; + } + + /** + * Get maxValue + * @return maxValue + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_MAX_VALUE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public Double getMaxValue() { + return maxValue; + } + + + @JsonProperty(JSON_PROPERTY_MAX_VALUE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setMaxValue(Double maxValue) { + this.maxValue = maxValue; + } + + + public ParameterDescription minValue(Double minValue) { + this.minValue = minValue; + return this; + } + + /** + * Get minValue + * @return minValue + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_MIN_VALUE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public Double getMinValue() { + return minValue; + } + + + @JsonProperty(JSON_PROPERTY_MIN_VALUE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setMinValue(Double minValue) { + this.minValue = minValue; + } + + + public ParameterDescription name(String name) { + this.name = name; + return this; + } + + /** + * Get name + * @return name + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_NAME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public String getName() { + return name; + } + + + @JsonProperty(JSON_PROPERTY_NAME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setName(String name) { + this.name = name; + } + + + public ParameterDescription scale(Double scale) { + this.scale = scale; + return this; + } + + /** + * Get scale + * @return scale + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_SCALE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public Double getScale() { + return scale; + } + + + @JsonProperty(JSON_PROPERTY_SCALE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setScale(Double scale) { + this.scale = scale; + } + + + /** + * Return true if this ParameterDescription object is equal to o. + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ParameterDescription parameterDescription = (ParameterDescription) o; + return Objects.equals(this.initialValue, parameterDescription.initialValue) && + Objects.equals(this.maxValue, parameterDescription.maxValue) && + Objects.equals(this.minValue, parameterDescription.minValue) && + Objects.equals(this.name, parameterDescription.name) && + Objects.equals(this.scale, parameterDescription.scale); + } + + @Override + public int hashCode() { + return Objects.hash(initialValue, maxValue, minValue, name, scale); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ParameterDescription {\n"); + sb.append(" initialValue: ").append(toIndentedString(initialValue)).append("\n"); + sb.append(" maxValue: ").append(toIndentedString(maxValue)).append("\n"); + sb.append(" minValue: ").append(toIndentedString(minValue)).append("\n"); + sb.append(" name: ").append(toIndentedString(name)).append("\n"); + sb.append(" scale: ").append(toIndentedString(scale)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + /** + * Convert the instance into URL query string. + * + * @return URL query string + */ + public String toUrlQueryString() { + return toUrlQueryString(null); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + String suffix = ""; + String containerSuffix = ""; + String containerPrefix = ""; + if (prefix == null) { + // style=form, explode=true, e.g. /pet?name=cat&type=manx + prefix = ""; + } else { + // deepObject style e.g. /pet?id[name]=cat&id[type]=manx + prefix = prefix + "["; + suffix = "]"; + containerSuffix = "]"; + containerPrefix = "["; + } + + StringJoiner joiner = new StringJoiner("&"); + + // add `initialValue` to the URL query string + if (getInitialValue() != null) { + joiner.add(String.format("%sinitialValue%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getInitialValue()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + // add `maxValue` to the URL query string + if (getMaxValue() != null) { + joiner.add(String.format("%smaxValue%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getMaxValue()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + // add `minValue` to the URL query string + if (getMinValue() != null) { + joiner.add(String.format("%sminValue%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getMinValue()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + // add `name` to the URL query string + if (getName() != null) { + joiner.add(String.format("%sname%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getName()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + // add `scale` to the URL query string + if (getScale() != null) { + joiner.add(String.format("%sscale%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getScale()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + return joiner.toString(); + } +} + diff --git a/vcell-restclient/src/main/java/org/vcell/restclient/model/ReferenceVariable.java b/vcell-restclient/src/main/java/org/vcell/restclient/model/ReferenceVariable.java new file mode 100644 index 0000000000..f3ad45601b --- /dev/null +++ b/vcell-restclient/src/main/java/org/vcell/restclient/model/ReferenceVariable.java @@ -0,0 +1,187 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.StringJoiner; +import java.util.Objects; +import java.util.Map; +import java.util.HashMap; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Arrays; +import org.vcell.restclient.model.ReferenceVariableReferenceVariableType; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + + +/** + * ReferenceVariable + */ +@JsonPropertyOrder({ + ReferenceVariable.JSON_PROPERTY_REFERENCE_VARIABLE_TYPE, + ReferenceVariable.JSON_PROPERTY_VAR_NAME +}) +@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen") +public class ReferenceVariable { + public static final String JSON_PROPERTY_REFERENCE_VARIABLE_TYPE = "referenceVariableType"; + private ReferenceVariableReferenceVariableType referenceVariableType; + + public static final String JSON_PROPERTY_VAR_NAME = "varName"; + private String varName; + + public ReferenceVariable() { + } + + public ReferenceVariable referenceVariableType(ReferenceVariableReferenceVariableType referenceVariableType) { + this.referenceVariableType = referenceVariableType; + return this; + } + + /** + * Get referenceVariableType + * @return referenceVariableType + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_REFERENCE_VARIABLE_TYPE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public ReferenceVariableReferenceVariableType getReferenceVariableType() { + return referenceVariableType; + } + + + @JsonProperty(JSON_PROPERTY_REFERENCE_VARIABLE_TYPE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setReferenceVariableType(ReferenceVariableReferenceVariableType referenceVariableType) { + this.referenceVariableType = referenceVariableType; + } + + + public ReferenceVariable varName(String varName) { + this.varName = varName; + return this; + } + + /** + * Get varName + * @return varName + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_VAR_NAME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public String getVarName() { + return varName; + } + + + @JsonProperty(JSON_PROPERTY_VAR_NAME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setVarName(String varName) { + this.varName = varName; + } + + + /** + * Return true if this ReferenceVariable object is equal to o. + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ReferenceVariable referenceVariable = (ReferenceVariable) o; + return Objects.equals(this.referenceVariableType, referenceVariable.referenceVariableType) && + Objects.equals(this.varName, referenceVariable.varName); + } + + @Override + public int hashCode() { + return Objects.hash(referenceVariableType, varName); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ReferenceVariable {\n"); + sb.append(" referenceVariableType: ").append(toIndentedString(referenceVariableType)).append("\n"); + sb.append(" varName: ").append(toIndentedString(varName)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + /** + * Convert the instance into URL query string. + * + * @return URL query string + */ + public String toUrlQueryString() { + return toUrlQueryString(null); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + String suffix = ""; + String containerSuffix = ""; + String containerPrefix = ""; + if (prefix == null) { + // style=form, explode=true, e.g. /pet?name=cat&type=manx + prefix = ""; + } else { + // deepObject style e.g. /pet?id[name]=cat&id[type]=manx + prefix = prefix + "["; + suffix = "]"; + containerSuffix = "]"; + containerPrefix = "["; + } + + StringJoiner joiner = new StringJoiner("&"); + + // add `referenceVariableType` to the URL query string + if (getReferenceVariableType() != null) { + joiner.add(String.format("%sreferenceVariableType%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getReferenceVariableType()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + // add `varName` to the URL query string + if (getVarName() != null) { + joiner.add(String.format("%svarName%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getVarName()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + return joiner.toString(); + } +} + diff --git a/vcell-restclient/src/main/java/org/vcell/restclient/model/ReferenceVariableReferenceVariableType.java b/vcell-restclient/src/main/java/org/vcell/restclient/model/ReferenceVariableReferenceVariableType.java new file mode 100644 index 0000000000..a8ffb678c8 --- /dev/null +++ b/vcell-restclient/src/main/java/org/vcell/restclient/model/ReferenceVariableReferenceVariableType.java @@ -0,0 +1,78 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.StringJoiner; +import java.util.Objects; +import java.util.Map; +import java.util.HashMap; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Gets or Sets ReferenceVariableReferenceVariableType + */ +public enum ReferenceVariableReferenceVariableType { + + DEPENDENT("dependent"), + + INDEPENDENT("independent"); + + private String value; + + ReferenceVariableReferenceVariableType(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + @JsonCreator + public static ReferenceVariableReferenceVariableType fromValue(String value) { + for (ReferenceVariableReferenceVariableType b : ReferenceVariableReferenceVariableType.values()) { + if (b.value.equals(value)) { + return b; + } + } + throw new IllegalArgumentException("Unexpected value '" + value + "'"); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + if (prefix == null) { + prefix = ""; + } + + return String.format("%s=%s", prefix, this.toString()); + } + +} + diff --git a/vcell-restclient/src/main/java/org/vcell/restclient/model/Vcellopt.java b/vcell-restclient/src/main/java/org/vcell/restclient/model/Vcellopt.java new file mode 100644 index 0000000000..6cacdff16b --- /dev/null +++ b/vcell-restclient/src/main/java/org/vcell/restclient/model/Vcellopt.java @@ -0,0 +1,261 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.StringJoiner; +import java.util.Objects; +import java.util.Map; +import java.util.HashMap; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Arrays; +import org.vcell.restclient.model.OptProblem; +import org.vcell.restclient.model.OptResultSet; +import org.vcell.restclient.model.VcelloptStatus; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + + +/** + * Vcellopt + */ +@JsonPropertyOrder({ + Vcellopt.JSON_PROPERTY_OPT_PROBLEM, + Vcellopt.JSON_PROPERTY_OPT_RESULT_SET, + Vcellopt.JSON_PROPERTY_STATUS, + Vcellopt.JSON_PROPERTY_STATUS_MESSAGE +}) +@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen") +public class Vcellopt { + public static final String JSON_PROPERTY_OPT_PROBLEM = "optProblem"; + private OptProblem optProblem; + + public static final String JSON_PROPERTY_OPT_RESULT_SET = "optResultSet"; + private OptResultSet optResultSet; + + public static final String JSON_PROPERTY_STATUS = "status"; + private VcelloptStatus status; + + public static final String JSON_PROPERTY_STATUS_MESSAGE = "statusMessage"; + private String statusMessage; + + public Vcellopt() { + } + + public Vcellopt optProblem(OptProblem optProblem) { + this.optProblem = optProblem; + return this; + } + + /** + * Get optProblem + * @return optProblem + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_OPT_PROBLEM) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public OptProblem getOptProblem() { + return optProblem; + } + + + @JsonProperty(JSON_PROPERTY_OPT_PROBLEM) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setOptProblem(OptProblem optProblem) { + this.optProblem = optProblem; + } + + + public Vcellopt optResultSet(OptResultSet optResultSet) { + this.optResultSet = optResultSet; + return this; + } + + /** + * Get optResultSet + * @return optResultSet + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_OPT_RESULT_SET) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public OptResultSet getOptResultSet() { + return optResultSet; + } + + + @JsonProperty(JSON_PROPERTY_OPT_RESULT_SET) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setOptResultSet(OptResultSet optResultSet) { + this.optResultSet = optResultSet; + } + + + public Vcellopt status(VcelloptStatus status) { + this.status = status; + return this; + } + + /** + * Get status + * @return status + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_STATUS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public VcelloptStatus getStatus() { + return status; + } + + + @JsonProperty(JSON_PROPERTY_STATUS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setStatus(VcelloptStatus status) { + this.status = status; + } + + + public Vcellopt statusMessage(String statusMessage) { + this.statusMessage = statusMessage; + return this; + } + + /** + * Get statusMessage + * @return statusMessage + **/ + @javax.annotation.Nullable + @JsonProperty(JSON_PROPERTY_STATUS_MESSAGE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public String getStatusMessage() { + return statusMessage; + } + + + @JsonProperty(JSON_PROPERTY_STATUS_MESSAGE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setStatusMessage(String statusMessage) { + this.statusMessage = statusMessage; + } + + + /** + * Return true if this Vcellopt object is equal to o. + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Vcellopt vcellopt = (Vcellopt) o; + return Objects.equals(this.optProblem, vcellopt.optProblem) && + Objects.equals(this.optResultSet, vcellopt.optResultSet) && + Objects.equals(this.status, vcellopt.status) && + Objects.equals(this.statusMessage, vcellopt.statusMessage); + } + + @Override + public int hashCode() { + return Objects.hash(optProblem, optResultSet, status, statusMessage); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class Vcellopt {\n"); + sb.append(" optProblem: ").append(toIndentedString(optProblem)).append("\n"); + sb.append(" optResultSet: ").append(toIndentedString(optResultSet)).append("\n"); + sb.append(" status: ").append(toIndentedString(status)).append("\n"); + sb.append(" statusMessage: ").append(toIndentedString(statusMessage)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + /** + * Convert the instance into URL query string. + * + * @return URL query string + */ + public String toUrlQueryString() { + return toUrlQueryString(null); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + String suffix = ""; + String containerSuffix = ""; + String containerPrefix = ""; + if (prefix == null) { + // style=form, explode=true, e.g. /pet?name=cat&type=manx + prefix = ""; + } else { + // deepObject style e.g. /pet?id[name]=cat&id[type]=manx + prefix = prefix + "["; + suffix = "]"; + containerSuffix = "]"; + containerPrefix = "["; + } + + StringJoiner joiner = new StringJoiner("&"); + + // add `optProblem` to the URL query string + if (getOptProblem() != null) { + joiner.add(getOptProblem().toUrlQueryString(prefix + "optProblem" + suffix)); + } + + // add `optResultSet` to the URL query string + if (getOptResultSet() != null) { + joiner.add(getOptResultSet().toUrlQueryString(prefix + "optResultSet" + suffix)); + } + + // add `status` to the URL query string + if (getStatus() != null) { + joiner.add(String.format("%sstatus%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getStatus()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + // add `statusMessage` to the URL query string + if (getStatusMessage() != null) { + joiner.add(String.format("%sstatusMessage%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getStatusMessage()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + + return joiner.toString(); + } +} + diff --git a/vcell-restclient/src/main/java/org/vcell/restclient/model/VcelloptStatus.java b/vcell-restclient/src/main/java/org/vcell/restclient/model/VcelloptStatus.java new file mode 100644 index 0000000000..37a46b9fcf --- /dev/null +++ b/vcell-restclient/src/main/java/org/vcell/restclient/model/VcelloptStatus.java @@ -0,0 +1,82 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.StringJoiner; +import java.util.Objects; +import java.util.Map; +import java.util.HashMap; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Gets or Sets VcelloptStatus + */ +public enum VcelloptStatus { + + COMPLETE("complete"), + + FAILED("failed"), + + QUEUED("queued"), + + RUNNING("running"); + + private String value; + + VcelloptStatus(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + @JsonCreator + public static VcelloptStatus fromValue(String value) { + for (VcelloptStatus b : VcelloptStatus.values()) { + if (b.value.equals(value)) { + return b; + } + } + throw new IllegalArgumentException("Unexpected value '" + value + "'"); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + if (prefix == null) { + prefix = ""; + } + + return String.format("%s=%s", prefix, this.toString()); + } + +} + diff --git a/vcell-restclient/src/test/java/org/vcell/restclient/api/OptimizationResourceApiTest.java b/vcell-restclient/src/test/java/org/vcell/restclient/api/OptimizationResourceApiTest.java new file mode 100644 index 0000000000..93c694fe51 --- /dev/null +++ b/vcell-restclient/src/test/java/org/vcell/restclient/api/OptimizationResourceApiTest.java @@ -0,0 +1,106 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.api; + +import org.vcell.restclient.ApiException; +import org.vcell.restclient.model.OptProblem; +import org.vcell.restclient.model.OptimizationJobStatus; +import org.vcell.restclient.model.VCellHTTPError; +import org.junit.Test; +import org.junit.Ignore; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + + +/** + * API tests for OptimizationResourceApi + */ +@Ignore +public class OptimizationResourceApiTest { + + private final OptimizationResourceApi api = new OptimizationResourceApi(); + + + /** + * Get status, progress, or results of an optimization job + * + * + * + * @throws ApiException + * if the Api call fails + */ + @Test + public void getOptimizationStatusTest() throws ApiException { + Long optId = null; + OptimizationJobStatus response = + api.getOptimizationStatus(optId); + + // TODO: test validations + } + + /** + * List optimization jobs for the authenticated user + * + * + * + * @throws ApiException + * if the Api call fails + */ + @Test + public void listOptimizationJobsTest() throws ApiException { + List response = + api.listOptimizationJobs(); + + // TODO: test validations + } + + /** + * Stop a running optimization job + * + * + * + * @throws ApiException + * if the Api call fails + */ + @Test + public void stopOptimizationTest() throws ApiException { + Long optId = null; + OptimizationJobStatus response = + api.stopOptimization(optId); + + // TODO: test validations + } + + /** + * Submit a new parameter estimation optimization job + * + * + * + * @throws ApiException + * if the Api call fails + */ + @Test + public void submitOptimizationTest() throws ApiException { + OptProblem optProblem = null; + OptimizationJobStatus response = + api.submitOptimization(optProblem); + + // TODO: test validations + } + +} diff --git a/vcell-restclient/src/test/java/org/vcell/restclient/model/CopasiOptimizationMethodOptimizationMethodTypeTest.java b/vcell-restclient/src/test/java/org/vcell/restclient/model/CopasiOptimizationMethodOptimizationMethodTypeTest.java new file mode 100644 index 0000000000..3acef8f6b9 --- /dev/null +++ b/vcell-restclient/src/test/java/org/vcell/restclient/model/CopasiOptimizationMethodOptimizationMethodTypeTest.java @@ -0,0 +1,32 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Model tests for CopasiOptimizationMethodOptimizationMethodType + */ +public class CopasiOptimizationMethodOptimizationMethodTypeTest { + /** + * Model tests for CopasiOptimizationMethodOptimizationMethodType + */ + @Test + public void testCopasiOptimizationMethodOptimizationMethodType() { + // TODO: test CopasiOptimizationMethodOptimizationMethodType + } + +} diff --git a/vcell-restclient/src/test/java/org/vcell/restclient/model/CopasiOptimizationMethodTest.java b/vcell-restclient/src/test/java/org/vcell/restclient/model/CopasiOptimizationMethodTest.java new file mode 100644 index 0000000000..e1777f5784 --- /dev/null +++ b/vcell-restclient/src/test/java/org/vcell/restclient/model/CopasiOptimizationMethodTest.java @@ -0,0 +1,60 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.vcell.restclient.model.CopasiOptimizationMethodOptimizationMethodType; +import org.vcell.restclient.model.CopasiOptimizationParameter; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Model tests for CopasiOptimizationMethod + */ +public class CopasiOptimizationMethodTest { + private final CopasiOptimizationMethod model = new CopasiOptimizationMethod(); + + /** + * Model tests for CopasiOptimizationMethod + */ + @Test + public void testCopasiOptimizationMethod() { + // TODO: test CopasiOptimizationMethod + } + + /** + * Test the property 'optimizationMethodType' + */ + @Test + public void optimizationMethodTypeTest() { + // TODO: test optimizationMethodType + } + + /** + * Test the property 'optimizationParameter' + */ + @Test + public void optimizationParameterTest() { + // TODO: test optimizationParameter + } + +} diff --git a/vcell-restclient/src/test/java/org/vcell/restclient/model/CopasiOptimizationParameterDataTypeTest.java b/vcell-restclient/src/test/java/org/vcell/restclient/model/CopasiOptimizationParameterDataTypeTest.java new file mode 100644 index 0000000000..065f4d0173 --- /dev/null +++ b/vcell-restclient/src/test/java/org/vcell/restclient/model/CopasiOptimizationParameterDataTypeTest.java @@ -0,0 +1,32 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Model tests for CopasiOptimizationParameterDataType + */ +public class CopasiOptimizationParameterDataTypeTest { + /** + * Model tests for CopasiOptimizationParameterDataType + */ + @Test + public void testCopasiOptimizationParameterDataType() { + // TODO: test CopasiOptimizationParameterDataType + } + +} diff --git a/vcell-restclient/src/test/java/org/vcell/restclient/model/CopasiOptimizationParameterParamTypeTest.java b/vcell-restclient/src/test/java/org/vcell/restclient/model/CopasiOptimizationParameterParamTypeTest.java new file mode 100644 index 0000000000..b2d4b63f0c --- /dev/null +++ b/vcell-restclient/src/test/java/org/vcell/restclient/model/CopasiOptimizationParameterParamTypeTest.java @@ -0,0 +1,32 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Model tests for CopasiOptimizationParameterParamType + */ +public class CopasiOptimizationParameterParamTypeTest { + /** + * Model tests for CopasiOptimizationParameterParamType + */ + @Test + public void testCopasiOptimizationParameterParamType() { + // TODO: test CopasiOptimizationParameterParamType + } + +} diff --git a/vcell-restclient/src/test/java/org/vcell/restclient/model/CopasiOptimizationParameterTest.java b/vcell-restclient/src/test/java/org/vcell/restclient/model/CopasiOptimizationParameterTest.java new file mode 100644 index 0000000000..9c7bf8899a --- /dev/null +++ b/vcell-restclient/src/test/java/org/vcell/restclient/model/CopasiOptimizationParameterTest.java @@ -0,0 +1,66 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Arrays; +import org.vcell.restclient.model.CopasiOptimizationParameterDataType; +import org.vcell.restclient.model.CopasiOptimizationParameterParamType; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Model tests for CopasiOptimizationParameter + */ +public class CopasiOptimizationParameterTest { + private final CopasiOptimizationParameter model = new CopasiOptimizationParameter(); + + /** + * Model tests for CopasiOptimizationParameter + */ + @Test + public void testCopasiOptimizationParameter() { + // TODO: test CopasiOptimizationParameter + } + + /** + * Test the property 'dataType' + */ + @Test + public void dataTypeTest() { + // TODO: test dataType + } + + /** + * Test the property 'paramType' + */ + @Test + public void paramTypeTest() { + // TODO: test paramType + } + + /** + * Test the property 'value' + */ + @Test + public void valueTest() { + // TODO: test value + } + +} diff --git a/vcell-restclient/src/test/java/org/vcell/restclient/model/OptJobStatusTest.java b/vcell-restclient/src/test/java/org/vcell/restclient/model/OptJobStatusTest.java new file mode 100644 index 0000000000..020dee4983 --- /dev/null +++ b/vcell-restclient/src/test/java/org/vcell/restclient/model/OptJobStatusTest.java @@ -0,0 +1,32 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Model tests for OptJobStatus + */ +public class OptJobStatusTest { + /** + * Model tests for OptJobStatus + */ + @Test + public void testOptJobStatus() { + // TODO: test OptJobStatus + } + +} diff --git a/vcell-restclient/src/test/java/org/vcell/restclient/model/OptProblemTest.java b/vcell-restclient/src/test/java/org/vcell/restclient/model/OptProblemTest.java new file mode 100644 index 0000000000..ceb4fce04c --- /dev/null +++ b/vcell-restclient/src/test/java/org/vcell/restclient/model/OptProblemTest.java @@ -0,0 +1,93 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.vcell.restclient.model.CopasiOptimizationMethod; +import org.vcell.restclient.model.ParameterDescription; +import org.vcell.restclient.model.ReferenceVariable; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Model tests for OptProblem + */ +public class OptProblemTest { + private final OptProblem model = new OptProblem(); + + /** + * Model tests for OptProblem + */ + @Test + public void testOptProblem() { + // TODO: test OptProblem + } + + /** + * Test the property 'copasiOptimizationMethod' + */ + @Test + public void copasiOptimizationMethodTest() { + // TODO: test copasiOptimizationMethod + } + + /** + * Test the property 'dataSet' + */ + @Test + public void dataSetTest() { + // TODO: test dataSet + } + + /** + * Test the property 'mathModelSbmlContents' + */ + @Test + public void mathModelSbmlContentsTest() { + // TODO: test mathModelSbmlContents + } + + /** + * Test the property 'numberOfOptimizationRuns' + */ + @Test + public void numberOfOptimizationRunsTest() { + // TODO: test numberOfOptimizationRuns + } + + /** + * Test the property 'parameterDescriptionList' + */ + @Test + public void parameterDescriptionListTest() { + // TODO: test parameterDescriptionList + } + + /** + * Test the property 'referenceVariable' + */ + @Test + public void referenceVariableTest() { + // TODO: test referenceVariable + } + +} diff --git a/vcell-restclient/src/test/java/org/vcell/restclient/model/OptProgressItemTest.java b/vcell-restclient/src/test/java/org/vcell/restclient/model/OptProgressItemTest.java new file mode 100644 index 0000000000..f78fde8987 --- /dev/null +++ b/vcell-restclient/src/test/java/org/vcell/restclient/model/OptProgressItemTest.java @@ -0,0 +1,56 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Arrays; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Model tests for OptProgressItem + */ +public class OptProgressItemTest { + private final OptProgressItem model = new OptProgressItem(); + + /** + * Model tests for OptProgressItem + */ + @Test + public void testOptProgressItem() { + // TODO: test OptProgressItem + } + + /** + * Test the property 'numFunctionEvaluations' + */ + @Test + public void numFunctionEvaluationsTest() { + // TODO: test numFunctionEvaluations + } + + /** + * Test the property 'objFuncValue' + */ + @Test + public void objFuncValueTest() { + // TODO: test objFuncValue + } + +} diff --git a/vcell-restclient/src/test/java/org/vcell/restclient/model/OptProgressReportTest.java b/vcell-restclient/src/test/java/org/vcell/restclient/model/OptProgressReportTest.java new file mode 100644 index 0000000000..0d517b3c06 --- /dev/null +++ b/vcell-restclient/src/test/java/org/vcell/restclient/model/OptProgressReportTest.java @@ -0,0 +1,61 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.vcell.restclient.model.OptProgressItem; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Model tests for OptProgressReport + */ +public class OptProgressReportTest { + private final OptProgressReport model = new OptProgressReport(); + + /** + * Model tests for OptProgressReport + */ + @Test + public void testOptProgressReport() { + // TODO: test OptProgressReport + } + + /** + * Test the property 'bestParamValues' + */ + @Test + public void bestParamValuesTest() { + // TODO: test bestParamValues + } + + /** + * Test the property 'progressItems' + */ + @Test + public void progressItemsTest() { + // TODO: test progressItems + } + +} diff --git a/vcell-restclient/src/test/java/org/vcell/restclient/model/OptResultSetTest.java b/vcell-restclient/src/test/java/org/vcell/restclient/model/OptResultSetTest.java new file mode 100644 index 0000000000..0d97a196b6 --- /dev/null +++ b/vcell-restclient/src/test/java/org/vcell/restclient/model/OptResultSetTest.java @@ -0,0 +1,75 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import org.vcell.restclient.model.OptProgressReport; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Model tests for OptResultSet + */ +public class OptResultSetTest { + private final OptResultSet model = new OptResultSet(); + + /** + * Model tests for OptResultSet + */ + @Test + public void testOptResultSet() { + // TODO: test OptResultSet + } + + /** + * Test the property 'numFunctionEvaluations' + */ + @Test + public void numFunctionEvaluationsTest() { + // TODO: test numFunctionEvaluations + } + + /** + * Test the property 'objectiveFunction' + */ + @Test + public void objectiveFunctionTest() { + // TODO: test objectiveFunction + } + + /** + * Test the property 'optParameterValues' + */ + @Test + public void optParameterValuesTest() { + // TODO: test optParameterValues + } + + /** + * Test the property 'optProgressReport' + */ + @Test + public void optProgressReportTest() { + // TODO: test optProgressReport + } + +} diff --git a/vcell-restclient/src/test/java/org/vcell/restclient/model/OptimizationJobStatusTest.java b/vcell-restclient/src/test/java/org/vcell/restclient/model/OptimizationJobStatusTest.java new file mode 100644 index 0000000000..d2224b8487 --- /dev/null +++ b/vcell-restclient/src/test/java/org/vcell/restclient/model/OptimizationJobStatusTest.java @@ -0,0 +1,91 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Arrays; +import org.vcell.restclient.model.OptJobStatus; +import org.vcell.restclient.model.OptProgressReport; +import org.vcell.restclient.model.Vcellopt; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Model tests for OptimizationJobStatus + */ +public class OptimizationJobStatusTest { + private final OptimizationJobStatus model = new OptimizationJobStatus(); + + /** + * Model tests for OptimizationJobStatus + */ + @Test + public void testOptimizationJobStatus() { + // TODO: test OptimizationJobStatus + } + + /** + * Test the property 'id' + */ + @Test + public void idTest() { + // TODO: test id + } + + /** + * Test the property 'status' + */ + @Test + public void statusTest() { + // TODO: test status + } + + /** + * Test the property 'statusMessage' + */ + @Test + public void statusMessageTest() { + // TODO: test statusMessage + } + + /** + * Test the property 'htcJobId' + */ + @Test + public void htcJobIdTest() { + // TODO: test htcJobId + } + + /** + * Test the property 'progressReport' + */ + @Test + public void progressReportTest() { + // TODO: test progressReport + } + + /** + * Test the property 'results' + */ + @Test + public void resultsTest() { + // TODO: test results + } + +} diff --git a/vcell-restclient/src/test/java/org/vcell/restclient/model/ParameterDescriptionTest.java b/vcell-restclient/src/test/java/org/vcell/restclient/model/ParameterDescriptionTest.java new file mode 100644 index 0000000000..c67c49a7a8 --- /dev/null +++ b/vcell-restclient/src/test/java/org/vcell/restclient/model/ParameterDescriptionTest.java @@ -0,0 +1,80 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Arrays; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Model tests for ParameterDescription + */ +public class ParameterDescriptionTest { + private final ParameterDescription model = new ParameterDescription(); + + /** + * Model tests for ParameterDescription + */ + @Test + public void testParameterDescription() { + // TODO: test ParameterDescription + } + + /** + * Test the property 'initialValue' + */ + @Test + public void initialValueTest() { + // TODO: test initialValue + } + + /** + * Test the property 'maxValue' + */ + @Test + public void maxValueTest() { + // TODO: test maxValue + } + + /** + * Test the property 'minValue' + */ + @Test + public void minValueTest() { + // TODO: test minValue + } + + /** + * Test the property 'name' + */ + @Test + public void nameTest() { + // TODO: test name + } + + /** + * Test the property 'scale' + */ + @Test + public void scaleTest() { + // TODO: test scale + } + +} diff --git a/vcell-restclient/src/test/java/org/vcell/restclient/model/ReferenceVariableReferenceVariableTypeTest.java b/vcell-restclient/src/test/java/org/vcell/restclient/model/ReferenceVariableReferenceVariableTypeTest.java new file mode 100644 index 0000000000..da6182ae2e --- /dev/null +++ b/vcell-restclient/src/test/java/org/vcell/restclient/model/ReferenceVariableReferenceVariableTypeTest.java @@ -0,0 +1,32 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Model tests for ReferenceVariableReferenceVariableType + */ +public class ReferenceVariableReferenceVariableTypeTest { + /** + * Model tests for ReferenceVariableReferenceVariableType + */ + @Test + public void testReferenceVariableReferenceVariableType() { + // TODO: test ReferenceVariableReferenceVariableType + } + +} diff --git a/vcell-restclient/src/test/java/org/vcell/restclient/model/ReferenceVariableTest.java b/vcell-restclient/src/test/java/org/vcell/restclient/model/ReferenceVariableTest.java new file mode 100644 index 0000000000..b3f31436db --- /dev/null +++ b/vcell-restclient/src/test/java/org/vcell/restclient/model/ReferenceVariableTest.java @@ -0,0 +1,57 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Arrays; +import org.vcell.restclient.model.ReferenceVariableReferenceVariableType; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Model tests for ReferenceVariable + */ +public class ReferenceVariableTest { + private final ReferenceVariable model = new ReferenceVariable(); + + /** + * Model tests for ReferenceVariable + */ + @Test + public void testReferenceVariable() { + // TODO: test ReferenceVariable + } + + /** + * Test the property 'referenceVariableType' + */ + @Test + public void referenceVariableTypeTest() { + // TODO: test referenceVariableType + } + + /** + * Test the property 'varName' + */ + @Test + public void varNameTest() { + // TODO: test varName + } + +} diff --git a/vcell-restclient/src/test/java/org/vcell/restclient/model/VcelloptStatusTest.java b/vcell-restclient/src/test/java/org/vcell/restclient/model/VcelloptStatusTest.java new file mode 100644 index 0000000000..ff3b030c6e --- /dev/null +++ b/vcell-restclient/src/test/java/org/vcell/restclient/model/VcelloptStatusTest.java @@ -0,0 +1,32 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Model tests for VcelloptStatus + */ +public class VcelloptStatusTest { + /** + * Model tests for VcelloptStatus + */ + @Test + public void testVcelloptStatus() { + // TODO: test VcelloptStatus + } + +} diff --git a/vcell-restclient/src/test/java/org/vcell/restclient/model/VcelloptTest.java b/vcell-restclient/src/test/java/org/vcell/restclient/model/VcelloptTest.java new file mode 100644 index 0000000000..9bc8cff235 --- /dev/null +++ b/vcell-restclient/src/test/java/org/vcell/restclient/model/VcelloptTest.java @@ -0,0 +1,75 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Arrays; +import org.vcell.restclient.model.OptProblem; +import org.vcell.restclient.model.OptResultSet; +import org.vcell.restclient.model.VcelloptStatus; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Model tests for Vcellopt + */ +public class VcelloptTest { + private final Vcellopt model = new Vcellopt(); + + /** + * Model tests for Vcellopt + */ + @Test + public void testVcellopt() { + // TODO: test Vcellopt + } + + /** + * Test the property 'optProblem' + */ + @Test + public void optProblemTest() { + // TODO: test optProblem + } + + /** + * Test the property 'optResultSet' + */ + @Test + public void optResultSetTest() { + // TODO: test optResultSet + } + + /** + * Test the property 'status' + */ + @Test + public void statusTest() { + // TODO: test status + } + + /** + * Test the property 'statusMessage' + */ + @Test + public void statusMessageTest() { + // TODO: test statusMessage + } + +} diff --git a/webapp-ng/src/app/core/modules/openapi/.openapi-generator/FILES b/webapp-ng/src/app/core/modules/openapi/.openapi-generator/FILES index 3f284d27af..e6f4a26d5a 100644 --- a/webapp-ng/src/app/core/modules/openapi/.openapi-generator/FILES +++ b/webapp-ng/src/app/core/modules/openapi/.openapi-generator/FILES @@ -16,6 +16,8 @@ api/hello-world.service.ts api/hello-world.serviceInterface.ts api/math-model-resource.service.ts api/math-model-resource.serviceInterface.ts +api/optimization-resource.service.ts +api/optimization-resource.serviceInterface.ts api/publication-resource.service.ts api/publication-resource.serviceInterface.ts api/simulation-resource.service.ts @@ -42,6 +44,11 @@ model/biomodel-ref.ts model/composite-curve.ts model/control-point-curve.ts model/coordinate.ts +model/copasi-optimization-method-optimization-method-type.ts +model/copasi-optimization-method.ts +model/copasi-optimization-parameter-data-type.ts +model/copasi-optimization-parameter-param-type.ts +model/copasi-optimization-parameter.ts model/curve-selection-info.ts model/curve.ts model/data-identifier.ts @@ -77,10 +84,19 @@ model/mathmodel-ref.ts model/model-type.ts model/models.ts model/n5-export-request.ts +model/opt-job-status.ts +model/opt-problem.ts +model/opt-progress-item.ts +model/opt-progress-report.ts +model/opt-result-set.ts +model/optimization-job-status.ts model/origin.ts +model/parameter-description.ts model/publication-info.ts model/publication.ts model/publish-models-request.ts +model/reference-variable-reference-variable-type.ts +model/reference-variable.ts model/sampled-curve.ts model/scheduler-status.ts model/simulation-execution-status-record.ts @@ -115,6 +131,8 @@ model/variable-type.ts model/vc-document-type.ts model/vc-image-summary.ts model/vc-simulation-identifier.ts +model/vcellopt-status.ts +model/vcellopt.ts model/version-flag.ts model/version.ts param.ts diff --git a/webapp-ng/src/app/core/modules/openapi/api/api.ts b/webapp-ng/src/app/core/modules/openapi/api/api.ts index 633957d44d..a822c5f79b 100644 --- a/webapp-ng/src/app/core/modules/openapi/api/api.ts +++ b/webapp-ng/src/app/core/modules/openapi/api/api.ts @@ -19,6 +19,9 @@ export * from './hello-world.serviceInterface'; export * from './math-model-resource.service'; import { MathModelResourceService } from './math-model-resource.service'; export * from './math-model-resource.serviceInterface'; +export * from './optimization-resource.service'; +import { OptimizationResourceService } from './optimization-resource.service'; +export * from './optimization-resource.serviceInterface'; export * from './publication-resource.service'; import { PublicationResourceService } from './publication-resource.service'; export * from './publication-resource.serviceInterface'; @@ -34,4 +37,4 @@ export * from './users-resource.serviceInterface'; export * from './vc-image-resource.service'; import { VCImageResourceService } from './vc-image-resource.service'; export * from './vc-image-resource.serviceInterface'; -export const APIS = [AdminResourceService, BioModelResourceService, ExportResourceService, FieldDataResourceService, GeometryResourceService, HelloWorldService, MathModelResourceService, PublicationResourceService, SimulationResourceService, SolverResourceService, UsersResourceService, VCImageResourceService]; +export const APIS = [AdminResourceService, BioModelResourceService, ExportResourceService, FieldDataResourceService, GeometryResourceService, HelloWorldService, MathModelResourceService, OptimizationResourceService, PublicationResourceService, SimulationResourceService, SolverResourceService, UsersResourceService, VCImageResourceService]; diff --git a/webapp-ng/src/app/core/modules/openapi/api/optimization-resource.service.ts b/webapp-ng/src/app/core/modules/openapi/api/optimization-resource.service.ts new file mode 100644 index 0000000000..3f76a3400b --- /dev/null +++ b/webapp-ng/src/app/core/modules/openapi/api/optimization-resource.service.ts @@ -0,0 +1,360 @@ +/** + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +/* tslint:disable:no-unused-variable member-ordering */ + +import { Inject, Injectable, Optional } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpParams, + HttpResponse, HttpEvent, HttpParameterCodec, HttpContext + } from '@angular/common/http'; +import { CustomHttpParameterCodec } from '../encoder'; +import { Observable } from 'rxjs'; + +// @ts-ignore +import { OptProblem } from '../model/opt-problem'; +// @ts-ignore +import { OptimizationJobStatus } from '../model/optimization-job-status'; +// @ts-ignore +import { VCellHTTPError } from '../model/v-cell-http-error'; + +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; +import { Configuration } from '../configuration'; +import { + OptimizationResourceServiceInterface +} from './optimization-resource.serviceInterface'; + + + +@Injectable({ + providedIn: 'root' +}) +export class OptimizationResourceService implements OptimizationResourceServiceInterface { + + protected basePath = 'https://vcell.cam.uchc.edu'; + public defaultHeaders = new HttpHeaders(); + public configuration = new Configuration(); + public encoder: HttpParameterCodec; + + constructor(protected httpClient: HttpClient, @Optional()@Inject(BASE_PATH) basePath: string|string[], @Optional() configuration: Configuration) { + if (configuration) { + this.configuration = configuration; + } + if (typeof this.configuration.basePath !== 'string') { + if (Array.isArray(basePath) && basePath.length > 0) { + basePath = basePath[0]; + } + + if (typeof basePath !== 'string') { + basePath = this.basePath; + } + this.configuration.basePath = basePath; + } + this.encoder = this.configuration.encoder || new CustomHttpParameterCodec(); + } + + + // @ts-ignore + private addToHttpParams(httpParams: HttpParams, value: any, key?: string): HttpParams { + if (typeof value === "object" && value instanceof Date === false) { + httpParams = this.addToHttpParamsRecursive(httpParams, value); + } else { + httpParams = this.addToHttpParamsRecursive(httpParams, value, key); + } + return httpParams; + } + + private addToHttpParamsRecursive(httpParams: HttpParams, value?: any, key?: string): HttpParams { + if (value == null) { + return httpParams; + } + + if (typeof value === "object") { + if (Array.isArray(value)) { + (value as any[]).forEach( elem => httpParams = this.addToHttpParamsRecursive(httpParams, elem, key)); + } else if (value instanceof Date) { + if (key != null) { + httpParams = httpParams.append(key, (value as Date).toISOString().substring(0, 10)); + } else { + throw Error("key may not be null if value is Date"); + } + } else { + Object.keys(value).forEach( k => httpParams = this.addToHttpParamsRecursive( + httpParams, value[k], key != null ? `${key}.${k}` : k)); + } + } else if (key != null) { + httpParams = httpParams.append(key, value); + } else { + throw Error("key may not be null if value is not object or array"); + } + return httpParams; + } + + /** + * Get status, progress, or results of an optimization job + * @param optId + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public getOptimizationStatus(optId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable; + public getOptimizationStatus(optId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public getOptimizationStatus(optId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public getOptimizationStatus(optId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable { + if (optId === null || optId === undefined) { + throw new Error('Required parameter optId was null or undefined when calling getOptimizationStatus.'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (openId) required + localVarCredential = this.configuration.lookupCredential('openId'); + if (localVarCredential) { + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/api/v1/optimization/${this.configuration.encodeParam({name: "optId", value: optId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: "int64"})}`; + return this.httpClient.request('get', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + reportProgress: reportProgress + } + ); + } + + /** + * List optimization jobs for the authenticated user + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public listOptimizationJobs(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public listOptimizationJobs(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; + public listOptimizationJobs(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; + public listOptimizationJobs(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable { + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (openId) required + localVarCredential = this.configuration.lookupCredential('openId'); + if (localVarCredential) { + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/api/v1/optimization`; + return this.httpClient.request>('get', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + reportProgress: reportProgress + } + ); + } + + /** + * Stop a running optimization job + * @param optId + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public stopOptimization(optId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable; + public stopOptimization(optId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public stopOptimization(optId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public stopOptimization(optId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable { + if (optId === null || optId === undefined) { + throw new Error('Required parameter optId was null or undefined when calling stopOptimization.'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (openId) required + localVarCredential = this.configuration.lookupCredential('openId'); + if (localVarCredential) { + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/api/v1/optimization/${this.configuration.encodeParam({name: "optId", value: optId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: "int64"})}/stop`; + return this.httpClient.request('post', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + reportProgress: reportProgress + } + ); + } + + /** + * Submit a new parameter estimation optimization job + * @param optProblem + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public submitOptimization(optProblem?: OptProblem, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable; + public submitOptimization(optProblem?: OptProblem, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public submitOptimization(optProblem?: OptProblem, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public submitOptimization(optProblem?: OptProblem, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable { + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (openId) required + localVarCredential = this.configuration.lookupCredential('openId'); + if (localVarCredential) { + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + + // to determine the Content-Type header + const consumes: string[] = [ + 'application/json' + ]; + const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes); + if (httpContentTypeSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected); + } + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/api/v1/optimization`; + return this.httpClient.request('post', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + body: optProblem, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + reportProgress: reportProgress + } + ); + } + +} diff --git a/webapp-ng/src/app/core/modules/openapi/api/optimization-resource.serviceInterface.ts b/webapp-ng/src/app/core/modules/openapi/api/optimization-resource.serviceInterface.ts new file mode 100644 index 0000000000..83e8f4556a --- /dev/null +++ b/webapp-ng/src/app/core/modules/openapi/api/optimization-resource.serviceInterface.ts @@ -0,0 +1,56 @@ +/** + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { HttpHeaders } from '@angular/common/http'; + +import { Observable } from 'rxjs'; + +import { OptProblem } from '../model/models'; +import { OptimizationJobStatus } from '../model/models'; +import { VCellHTTPError } from '../model/models'; + + +import { Configuration } from '../configuration'; + + + +export interface OptimizationResourceServiceInterface { + defaultHeaders: HttpHeaders; + configuration: Configuration; + + /** + * Get status, progress, or results of an optimization job + * + * @param optId + */ + getOptimizationStatus(optId: number, extraHttpRequestParams?: any): Observable; + + /** + * List optimization jobs for the authenticated user + * + */ + listOptimizationJobs(extraHttpRequestParams?: any): Observable>; + + /** + * Stop a running optimization job + * + * @param optId + */ + stopOptimization(optId: number, extraHttpRequestParams?: any): Observable; + + /** + * Submit a new parameter estimation optimization job + * + * @param optProblem + */ + submitOptimization(optProblem?: OptProblem, extraHttpRequestParams?: any): Observable; + +} diff --git a/webapp-ng/src/app/core/modules/openapi/model/copasi-optimization-method-optimization-method-type.ts b/webapp-ng/src/app/core/modules/openapi/model/copasi-optimization-method-optimization-method-type.ts new file mode 100644 index 0000000000..1ccbfd2feb --- /dev/null +++ b/webapp-ng/src/app/core/modules/openapi/model/copasi-optimization-method-optimization-method-type.ts @@ -0,0 +1,31 @@ +/** + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export type CopasiOptimizationMethodOptimizationMethodType = 'SRES' | 'evolutionaryProgram' | 'geneticAlgorithm' | 'geneticAlgorithmSR' | 'hookeJeeves' | 'levenbergMarquardt' | 'nelderMead' | 'particleSwarm' | 'praxis' | 'randomSearch' | 'simulatedAnnealing' | 'steepestDescent' | 'truncatedNewton'; + +export const CopasiOptimizationMethodOptimizationMethodType = { + Sres: 'SRES' as CopasiOptimizationMethodOptimizationMethodType, + EvolutionaryProgram: 'evolutionaryProgram' as CopasiOptimizationMethodOptimizationMethodType, + GeneticAlgorithm: 'geneticAlgorithm' as CopasiOptimizationMethodOptimizationMethodType, + GeneticAlgorithmSr: 'geneticAlgorithmSR' as CopasiOptimizationMethodOptimizationMethodType, + HookeJeeves: 'hookeJeeves' as CopasiOptimizationMethodOptimizationMethodType, + LevenbergMarquardt: 'levenbergMarquardt' as CopasiOptimizationMethodOptimizationMethodType, + NelderMead: 'nelderMead' as CopasiOptimizationMethodOptimizationMethodType, + ParticleSwarm: 'particleSwarm' as CopasiOptimizationMethodOptimizationMethodType, + Praxis: 'praxis' as CopasiOptimizationMethodOptimizationMethodType, + RandomSearch: 'randomSearch' as CopasiOptimizationMethodOptimizationMethodType, + SimulatedAnnealing: 'simulatedAnnealing' as CopasiOptimizationMethodOptimizationMethodType, + SteepestDescent: 'steepestDescent' as CopasiOptimizationMethodOptimizationMethodType, + TruncatedNewton: 'truncatedNewton' as CopasiOptimizationMethodOptimizationMethodType +}; + diff --git a/webapp-ng/src/app/core/modules/openapi/model/copasi-optimization-method.ts b/webapp-ng/src/app/core/modules/openapi/model/copasi-optimization-method.ts new file mode 100644 index 0000000000..c2cc02fe54 --- /dev/null +++ b/webapp-ng/src/app/core/modules/openapi/model/copasi-optimization-method.ts @@ -0,0 +1,23 @@ +/** + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { CopasiOptimizationParameter } from './copasi-optimization-parameter'; +import { CopasiOptimizationMethodOptimizationMethodType } from './copasi-optimization-method-optimization-method-type'; + + +export interface CopasiOptimizationMethod { + optimizationMethodType?: CopasiOptimizationMethodOptimizationMethodType; + optimizationParameter?: Array; +} +export namespace CopasiOptimizationMethod { +} + + diff --git a/webapp-ng/src/app/core/modules/openapi/model/copasi-optimization-parameter-data-type.ts b/webapp-ng/src/app/core/modules/openapi/model/copasi-optimization-parameter-data-type.ts new file mode 100644 index 0000000000..7aaabe032b --- /dev/null +++ b/webapp-ng/src/app/core/modules/openapi/model/copasi-optimization-parameter-data-type.ts @@ -0,0 +1,20 @@ +/** + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export type CopasiOptimizationParameterDataType = 'double' | 'int'; + +export const CopasiOptimizationParameterDataType = { + Double: 'double' as CopasiOptimizationParameterDataType, + Int: 'int' as CopasiOptimizationParameterDataType +}; + diff --git a/webapp-ng/src/app/core/modules/openapi/model/copasi-optimization-parameter-param-type.ts b/webapp-ng/src/app/core/modules/openapi/model/copasi-optimization-parameter-param-type.ts new file mode 100644 index 0000000000..4a8adaf816 --- /dev/null +++ b/webapp-ng/src/app/core/modules/openapi/model/copasi-optimization-parameter-param-type.ts @@ -0,0 +1,32 @@ +/** + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export type CopasiOptimizationParameterParamType = 'coolingFactor' | 'iterationLimit' | 'numberOfGenerations' | 'numberOfIterations' | 'pf' | 'populationSize' | 'randomNumberGenerator' | 'rho' | 'scale' | 'seed' | 'startTemperature' | 'stdDeviation' | 'swarmSize' | 'tolerance'; + +export const CopasiOptimizationParameterParamType = { + CoolingFactor: 'coolingFactor' as CopasiOptimizationParameterParamType, + IterationLimit: 'iterationLimit' as CopasiOptimizationParameterParamType, + NumberOfGenerations: 'numberOfGenerations' as CopasiOptimizationParameterParamType, + NumberOfIterations: 'numberOfIterations' as CopasiOptimizationParameterParamType, + Pf: 'pf' as CopasiOptimizationParameterParamType, + PopulationSize: 'populationSize' as CopasiOptimizationParameterParamType, + RandomNumberGenerator: 'randomNumberGenerator' as CopasiOptimizationParameterParamType, + Rho: 'rho' as CopasiOptimizationParameterParamType, + Scale: 'scale' as CopasiOptimizationParameterParamType, + Seed: 'seed' as CopasiOptimizationParameterParamType, + StartTemperature: 'startTemperature' as CopasiOptimizationParameterParamType, + StdDeviation: 'stdDeviation' as CopasiOptimizationParameterParamType, + SwarmSize: 'swarmSize' as CopasiOptimizationParameterParamType, + Tolerance: 'tolerance' as CopasiOptimizationParameterParamType +}; + diff --git a/webapp-ng/src/app/core/modules/openapi/model/copasi-optimization-parameter.ts b/webapp-ng/src/app/core/modules/openapi/model/copasi-optimization-parameter.ts new file mode 100644 index 0000000000..ed460efc77 --- /dev/null +++ b/webapp-ng/src/app/core/modules/openapi/model/copasi-optimization-parameter.ts @@ -0,0 +1,24 @@ +/** + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { CopasiOptimizationParameterDataType } from './copasi-optimization-parameter-data-type'; +import { CopasiOptimizationParameterParamType } from './copasi-optimization-parameter-param-type'; + + +export interface CopasiOptimizationParameter { + dataType?: CopasiOptimizationParameterDataType; + paramType?: CopasiOptimizationParameterParamType; + value?: number; +} +export namespace CopasiOptimizationParameter { +} + + diff --git a/webapp-ng/src/app/core/modules/openapi/model/models.ts b/webapp-ng/src/app/core/modules/openapi/model/models.ts index 9bc33ff6b3..f86e9ddb15 100644 --- a/webapp-ng/src/app/core/modules/openapi/model/models.ts +++ b/webapp-ng/src/app/core/modules/openapi/model/models.ts @@ -10,6 +10,11 @@ export * from './biomodel-ref'; export * from './composite-curve'; export * from './control-point-curve'; export * from './coordinate'; +export * from './copasi-optimization-method'; +export * from './copasi-optimization-method-optimization-method-type'; +export * from './copasi-optimization-parameter'; +export * from './copasi-optimization-parameter-data-type'; +export * from './copasi-optimization-parameter-param-type'; export * from './curve'; export * from './curve-selection-info'; export * from './data-identifier'; @@ -44,10 +49,19 @@ export * from './math-type'; export * from './mathmodel-ref'; export * from './model-type'; export * from './n5-export-request'; +export * from './opt-job-status'; +export * from './opt-problem'; +export * from './opt-progress-item'; +export * from './opt-progress-report'; +export * from './opt-result-set'; +export * from './optimization-job-status'; export * from './origin'; +export * from './parameter-description'; export * from './publication'; export * from './publication-info'; export * from './publish-models-request'; +export * from './reference-variable'; +export * from './reference-variable-reference-variable-type'; export * from './specialclaim'; export * from './sampled-curve'; export * from './scheduler-status'; @@ -82,5 +96,7 @@ export * from './variable-domain'; export * from './variable-mode'; export * from './variable-specs'; export * from './variable-type'; +export * from './vcellopt'; +export * from './vcellopt-status'; export * from './version'; export * from './version-flag'; diff --git a/webapp-ng/src/app/core/modules/openapi/model/opt-job-status.ts b/webapp-ng/src/app/core/modules/openapi/model/opt-job-status.ts new file mode 100644 index 0000000000..355eaa0dd0 --- /dev/null +++ b/webapp-ng/src/app/core/modules/openapi/model/opt-job-status.ts @@ -0,0 +1,24 @@ +/** + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export type OptJobStatus = 'SUBMITTED' | 'QUEUED' | 'RUNNING' | 'COMPLETE' | 'FAILED' | 'STOPPED'; + +export const OptJobStatus = { + Submitted: 'SUBMITTED' as OptJobStatus, + Queued: 'QUEUED' as OptJobStatus, + Running: 'RUNNING' as OptJobStatus, + Complete: 'COMPLETE' as OptJobStatus, + Failed: 'FAILED' as OptJobStatus, + Stopped: 'STOPPED' as OptJobStatus +}; + diff --git a/webapp-ng/src/app/core/modules/openapi/model/opt-problem.ts b/webapp-ng/src/app/core/modules/openapi/model/opt-problem.ts new file mode 100644 index 0000000000..39f9f788ef --- /dev/null +++ b/webapp-ng/src/app/core/modules/openapi/model/opt-problem.ts @@ -0,0 +1,25 @@ +/** + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { CopasiOptimizationMethod } from './copasi-optimization-method'; +import { ReferenceVariable } from './reference-variable'; +import { ParameterDescription } from './parameter-description'; + + +export interface OptProblem { + copasiOptimizationMethod?: CopasiOptimizationMethod; + dataSet?: Array>; + mathModelSbmlContents?: string; + numberOfOptimizationRuns?: number; + parameterDescriptionList?: Array; + referenceVariable?: Array; +} + diff --git a/webapp-ng/src/app/core/modules/openapi/model/opt-progress-item.ts b/webapp-ng/src/app/core/modules/openapi/model/opt-progress-item.ts new file mode 100644 index 0000000000..45a2631cac --- /dev/null +++ b/webapp-ng/src/app/core/modules/openapi/model/opt-progress-item.ts @@ -0,0 +1,18 @@ +/** + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface OptProgressItem { + numFunctionEvaluations?: number; + objFuncValue?: number; +} + diff --git a/webapp-ng/src/app/core/modules/openapi/model/opt-progress-report.ts b/webapp-ng/src/app/core/modules/openapi/model/opt-progress-report.ts new file mode 100644 index 0000000000..069201725d --- /dev/null +++ b/webapp-ng/src/app/core/modules/openapi/model/opt-progress-report.ts @@ -0,0 +1,19 @@ +/** + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { OptProgressItem } from './opt-progress-item'; + + +export interface OptProgressReport { + bestParamValues?: { [key: string]: number; }; + progressItems?: Array; +} + diff --git a/webapp-ng/src/app/core/modules/openapi/model/opt-result-set.ts b/webapp-ng/src/app/core/modules/openapi/model/opt-result-set.ts new file mode 100644 index 0000000000..ac40abd3aa --- /dev/null +++ b/webapp-ng/src/app/core/modules/openapi/model/opt-result-set.ts @@ -0,0 +1,21 @@ +/** + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { OptProgressReport } from './opt-progress-report'; + + +export interface OptResultSet { + numFunctionEvaluations?: number; + objectiveFunction?: number; + optParameterValues?: { [key: string]: number; }; + optProgressReport?: OptProgressReport; +} + diff --git a/webapp-ng/src/app/core/modules/openapi/model/optimization-job-status.ts b/webapp-ng/src/app/core/modules/openapi/model/optimization-job-status.ts new file mode 100644 index 0000000000..90ad5d5a95 --- /dev/null +++ b/webapp-ng/src/app/core/modules/openapi/model/optimization-job-status.ts @@ -0,0 +1,28 @@ +/** + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { OptProgressReport } from './opt-progress-report'; +import { Vcellopt } from './vcellopt'; +import { OptJobStatus } from './opt-job-status'; + + +export interface OptimizationJobStatus { + id?: string; + status?: OptJobStatus; + statusMessage?: string; + htcJobId?: string; + progressReport?: OptProgressReport; + results?: Vcellopt; +} +export namespace OptimizationJobStatus { +} + + diff --git a/webapp-ng/src/app/core/modules/openapi/model/parameter-description.ts b/webapp-ng/src/app/core/modules/openapi/model/parameter-description.ts new file mode 100644 index 0000000000..aa892e34b8 --- /dev/null +++ b/webapp-ng/src/app/core/modules/openapi/model/parameter-description.ts @@ -0,0 +1,21 @@ +/** + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface ParameterDescription { + initialValue?: number; + maxValue?: number; + minValue?: number; + name?: string; + scale?: number; +} + diff --git a/webapp-ng/src/app/core/modules/openapi/model/reference-variable-reference-variable-type.ts b/webapp-ng/src/app/core/modules/openapi/model/reference-variable-reference-variable-type.ts new file mode 100644 index 0000000000..308273f6fd --- /dev/null +++ b/webapp-ng/src/app/core/modules/openapi/model/reference-variable-reference-variable-type.ts @@ -0,0 +1,20 @@ +/** + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export type ReferenceVariableReferenceVariableType = 'dependent' | 'independent'; + +export const ReferenceVariableReferenceVariableType = { + Dependent: 'dependent' as ReferenceVariableReferenceVariableType, + Independent: 'independent' as ReferenceVariableReferenceVariableType +}; + diff --git a/webapp-ng/src/app/core/modules/openapi/model/reference-variable.ts b/webapp-ng/src/app/core/modules/openapi/model/reference-variable.ts new file mode 100644 index 0000000000..8e5d012668 --- /dev/null +++ b/webapp-ng/src/app/core/modules/openapi/model/reference-variable.ts @@ -0,0 +1,22 @@ +/** + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { ReferenceVariableReferenceVariableType } from './reference-variable-reference-variable-type'; + + +export interface ReferenceVariable { + referenceVariableType?: ReferenceVariableReferenceVariableType; + varName?: string; +} +export namespace ReferenceVariable { +} + + diff --git a/webapp-ng/src/app/core/modules/openapi/model/vcellopt-status.ts b/webapp-ng/src/app/core/modules/openapi/model/vcellopt-status.ts new file mode 100644 index 0000000000..5c3474893f --- /dev/null +++ b/webapp-ng/src/app/core/modules/openapi/model/vcellopt-status.ts @@ -0,0 +1,22 @@ +/** + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export type VcelloptStatus = 'complete' | 'failed' | 'queued' | 'running'; + +export const VcelloptStatus = { + Complete: 'complete' as VcelloptStatus, + Failed: 'failed' as VcelloptStatus, + Queued: 'queued' as VcelloptStatus, + Running: 'running' as VcelloptStatus +}; + diff --git a/webapp-ng/src/app/core/modules/openapi/model/vcellopt.ts b/webapp-ng/src/app/core/modules/openapi/model/vcellopt.ts new file mode 100644 index 0000000000..3f57be355d --- /dev/null +++ b/webapp-ng/src/app/core/modules/openapi/model/vcellopt.ts @@ -0,0 +1,26 @@ +/** + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { VcelloptStatus } from './vcellopt-status'; +import { OptProblem } from './opt-problem'; +import { OptResultSet } from './opt-result-set'; + + +export interface Vcellopt { + optProblem?: OptProblem; + optResultSet?: OptResultSet; + status?: VcelloptStatus; + statusMessage?: string; +} +export namespace Vcellopt { +} + + From 7d62d428a24e8bdacdd1dc73402dd7745a0cc62c Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Wed, 8 Apr 2026 13:32:04 -0400 Subject: [PATCH 11/40] Add generated-client tests for optimization API Add 3 tests using the auto-generated OptimizationResourceApi from vcell-restclient. These exercise the same client library the desktop client will use, validating serialization round-trips: - Submit and get status via generated client - List jobs via generated client - Stop a running job via generated client Also serves as usage documentation for the generated API. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../vcell/restq/OptimizationResourceTest.java | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/vcell-rest/src/test/java/org/vcell/restq/OptimizationResourceTest.java b/vcell-rest/src/test/java/org/vcell/restq/OptimizationResourceTest.java index 50038cd0b7..1cbb5241d4 100644 --- a/vcell-rest/src/test/java/org/vcell/restq/OptimizationResourceTest.java +++ b/vcell-rest/src/test/java/org/vcell/restq/OptimizationResourceTest.java @@ -302,6 +302,129 @@ public void testUnauthenticatedAccess() { .statusCode(401); } + // ================================================================================== + // Tests using the auto-generated OptimizationResourceApi (vcell-restclient). + // These exercise the same client code path the desktop client will use. + // ================================================================================== + + @Test + @Order(10) + public void testGeneratedClient_submitAndGetStatus() throws Exception { + TestEndpointUtils.mapApiClientToAdmin(aliceAPIClient); + var optApi = new org.vcell.restclient.api.OptimizationResourceApi(aliceAPIClient); + + // Build OptProblem using the generated model types (org.vcell.restclient.model.*) + var optProblem = createGeneratedClientOptProblem(); + + // Submit + org.vcell.restclient.model.OptimizationJobStatus submitResult = optApi.submitOptimization(optProblem); + assertNotNull(submitResult.getId(), "Submit should return a job ID"); + assertEquals(org.vcell.restclient.model.OptJobStatus.SUBMITTED, submitResult.getStatus()); + assertNull(submitResult.getProgressReport()); + assertNull(submitResult.getResults()); + + // Get status by ID + Long jobId = Long.parseLong(submitResult.getId()); + org.vcell.restclient.model.OptimizationJobStatus getResult = optApi.getOptimizationStatus(jobId); + assertEquals(submitResult.getId(), getResult.getId()); + assertEquals(org.vcell.restclient.model.OptJobStatus.SUBMITTED, getResult.getStatus()); + } + + @Test + @Order(11) + public void testGeneratedClient_listJobs() throws Exception { + TestEndpointUtils.mapApiClientToAdmin(aliceAPIClient); + var optApi = new org.vcell.restclient.api.OptimizationResourceApi(aliceAPIClient); + + // Submit two jobs + var optProblem = createGeneratedClientOptProblem(); + optApi.submitOptimization(optProblem); + optApi.submitOptimization(optProblem); + + // List + java.util.List jobs = optApi.listOptimizationJobs(); + assertTrue(jobs.size() >= 2, "Expected at least 2 jobs, got " + jobs.size()); + for (var job : jobs) { + assertNotNull(job.getId()); + assertNotNull(job.getStatus()); + assertNull(job.getProgressReport(), "List should not include progressReport"); + assertNull(job.getResults(), "List should not include results"); + } + } + + @Test + @Order(12) + public void testGeneratedClient_stopJob() throws Exception { + TestEndpointUtils.mapApiClientToAdmin(aliceAPIClient); + var optApi = new org.vcell.restclient.api.OptimizationResourceApi(aliceAPIClient); + User adminUser = TestEndpointUtils.administratorUser; + + // Submit via generated client + var optProblem = createGeneratedClientOptProblem(); + org.vcell.restclient.model.OptimizationJobStatus submitResult = optApi.submitOptimization(optProblem); + Long jobId = Long.parseLong(submitResult.getId()); + + // Transition to RUNNING via the service (simulates vcell-submit status update) + KeyValue jobKey = new KeyValue(submitResult.getId()); + optimizationRestService.updateHtcJobId(jobKey, "SLURM:77777"); + optimizationRestService.updateOptJobStatus(jobKey, OptJobStatus.RUNNING, null); + + // Stop via generated client + org.vcell.restclient.model.OptimizationJobStatus stopResult = optApi.stopOptimization(jobId); + assertEquals(org.vcell.restclient.model.OptJobStatus.STOPPED, stopResult.getStatus()); + assertEquals("Stopped by user", stopResult.getStatusMessage()); + } + + /** + * Create an OptProblem using the generated client model types (org.vcell.restclient.model.*). + * This is the same pattern the desktop client will use. + */ + private org.vcell.restclient.model.OptProblem createGeneratedClientOptProblem() { + var optProblem = new org.vcell.restclient.model.OptProblem(); + optProblem.setMathModelSbmlContents("test"); + optProblem.setNumberOfOptimizationRuns(1); + + var p1 = new org.vcell.restclient.model.ParameterDescription(); + p1.setName("k1"); + p1.setMinValue(0.0); + p1.setMaxValue(10.0); + p1.setInitialValue(1.0); + p1.setScale(1.0); + + var p2 = new org.vcell.restclient.model.ParameterDescription(); + p2.setName("k2"); + p2.setMinValue(0.0); + p2.setMaxValue(10.0); + p2.setInitialValue(2.0); + p2.setScale(1.0); + + optProblem.setParameterDescriptionList(List.of(p1, p2)); + optProblem.setDataSet(List.of( + List.of(0.0, 1.0, 2.0), + List.of(1.0, 1.5, 2.5) + )); + + var rv1 = new org.vcell.restclient.model.ReferenceVariable(); + rv1.setVarName("t"); + rv1.setReferenceVariableType(org.vcell.restclient.model.ReferenceVariableReferenceVariableType.INDEPENDENT); + var rv2 = new org.vcell.restclient.model.ReferenceVariable(); + rv2.setVarName("x"); + rv2.setReferenceVariableType(org.vcell.restclient.model.ReferenceVariableReferenceVariableType.DEPENDENT); + + optProblem.setReferenceVariable(List.of(rv1, rv2)); + + var method = new org.vcell.restclient.model.CopasiOptimizationMethod(); + method.setOptimizationMethodType(org.vcell.restclient.model.CopasiOptimizationMethodOptimizationMethodType.EVOLUTIONARYPROGRAM); + method.setOptimizationParameter(List.of()); + optProblem.setCopasiOptimizationMethod(method); + + return optProblem; + } + + // ================================================================================== + // Helpers + // ================================================================================== + private OptProblem createTestOptProblem() { OptProblem optProblem = new OptProblem(); optProblem.setMathModelSbmlContents("test"); From 5fa8d8b98498c269fcfbabc9a1820538358b2841 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Wed, 8 Apr 2026 13:41:25 -0400 Subject: [PATCH 12/40] Update desktop client to use generated OptimizationResourceApi Rewrite CopasiOptimizationSolverRemote.solveRemoteApi() to use the auto-generated OptimizationResourceApi (vcell-restclient) instead of the legacy VCellApiClient.submitOptimization/getOptRunJson methods. Key improvements: - Typed OptimizationJobStatus response with explicit status enum, progressReport, and results fields - Replaces error-prone string-prefix parsing ("QUEUED:", "RUNNING:") - Separate stop endpoint (POST /{id}/stop) replaces bStop query param - Clean switch-based status handling Add getOptimizationApi() accessor to VCellApiClient. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../org/vcell/api/client/VCellApiClient.java | 2 + .../CopasiOptimizationSolverRemote.java | 236 ++++++++---------- 2 files changed, 100 insertions(+), 138 deletions(-) diff --git a/vcell-apiclient/src/main/java/org/vcell/api/client/VCellApiClient.java b/vcell-apiclient/src/main/java/org/vcell/api/client/VCellApiClient.java index ca8964a300..6210b68bac 100644 --- a/vcell-apiclient/src/main/java/org/vcell/api/client/VCellApiClient.java +++ b/vcell-apiclient/src/main/java/org/vcell/api/client/VCellApiClient.java @@ -341,6 +341,8 @@ public MathModelResourceApi getMathModelApi(){ public ExportResourceApi getExportApi(){return new ExportResourceApi(apiClient);} + public OptimizationResourceApi getOptimizationApi(){return new OptimizationResourceApi(apiClient);} + public String getVCellUserNameFromAuth0Mapping() throws ApiException { try { UsersResourceApi usersResourceApi = new UsersResourceApi(apiClient); diff --git a/vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java b/vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java index aff33dd3a6..64899d584c 100644 --- a/vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java +++ b/vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java @@ -1,14 +1,10 @@ package copasi; import cbit.vcell.client.ClientRequestManager; -import cbit.vcell.client.RequestManager; -import cbit.vcell.client.VCellClient; -import cbit.vcell.client.server.ClientServerInfo; import cbit.vcell.modelopt.ParameterEstimationTask; import cbit.vcell.opt.OptimizationException; import cbit.vcell.opt.OptimizationResultSet; import cbit.vcell.opt.OptimizationStatus; -import cbit.vcell.resource.PropertyLoader; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -16,6 +12,9 @@ import org.vcell.optimization.CopasiOptSolverCallbacks; import org.vcell.optimization.CopasiUtils; import org.vcell.optimization.jtd.*; +import org.vcell.restclient.api.OptimizationResourceApi; +import org.vcell.restclient.model.OptJobStatus; +import org.vcell.restclient.model.OptimizationJobStatus; import org.vcell.util.ClientTaskStatusSupport; import org.vcell.util.UserCancelException; @@ -30,184 +29,148 @@ public static OptimizationResultSet solveRemoteApi( ClientTaskStatusSupport clientTaskStatusSupport, ClientRequestManager requestManager) { - // return solveLocalPython(parameterEstimationTask); - try { - // e.g. vcell.serverhost=vcellapi.cam.uchc.edu:443 - VCellApiClient apiClient = requestManager.getClientServerManager().getVCellApiClient(); + VCellApiClient vcellApiClient = requestManager.getClientServerManager().getVCellApiClient(); + OptimizationResourceApi optApi = vcellApiClient.getOptimizationApi(); - OptProblem optProblem = CopasiUtils.paramTaskToOptProblem(parameterEstimationTask); + // Convert parameter estimation task to OptProblem (vcell-core types) + OptProblem optProblemCore = CopasiUtils.paramTaskToOptProblem(parameterEstimationTask); + // Convert to generated client model type for the API call ObjectMapper objectMapper = new ObjectMapper(); - String optProblemJson = objectMapper.writeValueAsString(optProblem); + String optProblemJson = objectMapper.writeValueAsString(optProblemCore); + org.vcell.restclient.model.OptProblem optProblem = objectMapper.readValue( + optProblemJson, org.vcell.restclient.model.OptProblem.class); if (clientTaskStatusSupport != null) { clientTaskStatusSupport.setMessage("Submitting opt problem..."); } - //Submit but allow user to get out from restlet blocking call - final String[] optIdHolder = new String[]{null}; - final Exception[] exceptHolder = new Exception[]{null}; - Thread submitThread = new Thread(() -> { - try { - optIdHolder[0] = apiClient.submitOptimization(optProblemJson); - lg.info("submitted optimization jobID="+optIdHolder[0]); - if (optSolverCallbacks.getStopRequested()) { - apiClient.getOptRunJson(optIdHolder[0], optSolverCallbacks.getStopRequested()); - lg.info("user cancelled optimization jobID="+optIdHolder[0]); - } - } catch (Exception e) { - lg.error(e.getMessage(), e); - exceptHolder[0] = e; - } - }); - submitThread.setDaemon(true); - submitThread.start(); - - // - // wait here until either failure to submit or submitted and retrieved Job ID - // - while (optIdHolder[0] == null && exceptHolder[0] == null && !optSolverCallbacks.getStopRequested()) { - try { - Thread.sleep(200); - }catch (InterruptedException e){} - } - - // - // failed to submit, throw the exception now - // - if (exceptHolder[0] != null) { - throw exceptHolder[0]; - } + // Submit optimization job + OptimizationJobStatus submitResult = optApi.submitOptimization(optProblem); + Long jobId = Long.parseLong(submitResult.getId()); + lg.info("submitted optimization jobID={}", jobId); - // - // loop to query status and collect results - // + // Poll for status and results final long TIMEOUT_MS = 1000 * 200; // 200 second timeout long startTime = System.currentTimeMillis(); if (clientTaskStatusSupport != null) { clientTaskStatusSupport.setMessage("Waiting for progress..."); } + Vcellopt optRun = null; OptProgressReport latestProgressReport = null; + while ((System.currentTimeMillis() - startTime) < TIMEOUT_MS) { - // - // check for user stop request - // - boolean bStopRequested = optSolverCallbacks.getStopRequested(); - if (bStopRequested) { - lg.info("user cancelled optimization jobID="+optIdHolder[0]); + // Check for user stop request + if (optSolverCallbacks.getStopRequested()) { + lg.info("user cancelled optimization jobID={}", jobId); try { - apiClient.getOptRunJson(optIdHolder[0], bStopRequested); - lg.info("requested job to be stopped jobID="+optIdHolder[0]); - }catch (Exception e){ + optApi.stopOptimization(jobId); + lg.info("requested job to be stopped jobID={}", jobId); + } catch (Exception e) { lg.error(e.getMessage(), e); - }finally{ - if (latestProgressReport!=null){ + } finally { + if (latestProgressReport != null) { if (clientTaskStatusSupport != null) { clientTaskStatusSupport.setProgress(100); } - OptimizationResultSet copasiOptimizationResultSet = CopasiUtils.getOptimizationResultSet(parameterEstimationTask, latestProgressReport); - return copasiOptimizationResultSet; + return CopasiUtils.getOptimizationResultSet(parameterEstimationTask, latestProgressReport); } } throw UserCancelException.CANCEL_GENERIC; } - String optRunServerMessage = apiClient.getOptRunJson(optIdHolder[0], false); + // Poll status + OptimizationJobStatus status = optApi.getOptimizationStatus(jobId); if (optSolverCallbacks.getStopRequested()) { throw UserCancelException.CANCEL_GENERIC; } - if (optRunServerMessage.startsWith(VcelloptStatus.QUEUED.name() + ":")) { - if (clientTaskStatusSupport != null) { - clientTaskStatusSupport.setMessage("Queued..."); - } - - } else if (optRunServerMessage.startsWith(VcelloptStatus.FAILED.name()+":") || optRunServerMessage.toLowerCase().startsWith("exception:")){ - if (clientTaskStatusSupport != null) { - clientTaskStatusSupport.setMessage(optRunServerMessage); - } - - } else if (optRunServerMessage.startsWith(VcelloptStatus.RUNNING.name() + ":")) { - if (clientTaskStatusSupport != null) { - clientTaskStatusSupport.setMessage("Running (waiting for progress) ..."); - } - } else { - // consider that optRunServerMessage is either a progress report (OptProgressReport) or a final solution (Vcellopt) - Object optObject = null; - try { - optObject = objectMapper.readValue(optRunServerMessage, Vcellopt.class); - }catch (Exception e){ - optObject = objectMapper.readValue(optRunServerMessage, OptProgressReport.class); - } - - if (optObject instanceof Vcellopt) { - // - // have final solution with progress report and analytics - // - optRun = (Vcellopt) optObject; - final OptProgressReport optProgressReport = optRun.getOptResultSet().getOptProgressReport(); - VcelloptStatus status = optRun.getStatus(); - if (optProgressReport != null){ - SwingUtilities.invokeLater(() -> optSolverCallbacks.setProgressReport(optProgressReport)); + switch (status.getStatus()) { + case SUBMITTED: + case QUEUED: + if (clientTaskStatusSupport != null) { + clientTaskStatusSupport.setMessage("Queued..."); + } + break; + + case RUNNING: + if (status.getProgressReport() != null) { + // Convert generated model progress report to vcell-core type + String progressJson = objectMapper.writeValueAsString(status.getProgressReport()); + latestProgressReport = objectMapper.readValue(progressJson, OptProgressReport.class); + final OptProgressReport progressReport = latestProgressReport; + SwingUtilities.invokeLater(() -> { + try { + optSolverCallbacks.setProgressReport(progressReport); + } catch (Exception e) { + lg.error("error updating progress", e); + } + if (clientTaskStatusSupport != null) { + clientTaskStatusSupport.setMessage("Running ..."); + } + }); + } else { + if (clientTaskStatusSupport != null) { + clientTaskStatusSupport.setMessage("Running (waiting for progress) ..."); + } } - if (status == VcelloptStatus.COMPLETE) { - lg.info("job " + optIdHolder[0] + ": status " + status + " " + optRun.getOptResultSet().toString()); + break; + + case COMPLETE: + if (status.getResults() != null) { + String resultsJson = objectMapper.writeValueAsString(status.getResults()); + optRun = objectMapper.readValue(resultsJson, Vcellopt.class); + // Update UI with final progress if available + if (optRun.getOptResultSet() != null && optRun.getOptResultSet().getOptProgressReport() != null) { + final OptProgressReport finalProgress = optRun.getOptResultSet().getOptProgressReport(); + SwingUtilities.invokeLater(() -> optSolverCallbacks.setProgressReport(finalProgress)); + } + lg.info("job {}: COMPLETE {}", jobId, optRun.getOptResultSet()); if (clientTaskStatusSupport != null) { clientTaskStatusSupport.setProgress(100); } - break; - } - if (status == VcelloptStatus.FAILED) { - String msg = "optimization failed, message=" + optRun.getStatusMessage(); - lg.error(msg); - throw new RuntimeException(msg); } - lg.info("job " + optIdHolder[0] + ": status " + status); - }else if (optObject instanceof OptProgressReport){ - // - // have intermediate progress report - // - latestProgressReport = (OptProgressReport) optObject; - final OptProgressReport progressReport = latestProgressReport; - SwingUtilities.invokeLater(() -> { - try { - optSolverCallbacks.setProgressReport(progressReport); - } catch (Exception e) { - lg.error(optRunServerMessage, e); - } + break; + + case FAILED: + String failMsg = "optimization failed, message=" + status.getStatusMessage(); + lg.error(failMsg); + throw new RuntimeException(failMsg); + + case STOPPED: + if (latestProgressReport != null) { if (clientTaskStatusSupport != null) { - int numFunctionEvaluations = 0; - if (progressReport.getProgressItems()!=null && progressReport.getProgressItems().size()>0){ - OptProgressItem lastItem = progressReport.getProgressItems().get(progressReport.getProgressItems().size()-1); - numFunctionEvaluations = lastItem.getNumFunctionEvaluations(); - } - clientTaskStatusSupport.setMessage("Running ..."); + clientTaskStatusSupport.setProgress(100); } - }); - } + return CopasiUtils.getOptimizationResultSet(parameterEstimationTask, latestProgressReport); + } + throw UserCancelException.CANCEL_GENERIC; + } + + if (optRun != null) { + break; // COMPLETE } + try { Thread.sleep(2000); - }catch (InterruptedException e){} + } catch (InterruptedException e) { /* ignore */ } } - if((System.currentTimeMillis()-startTime) >= TIMEOUT_MS) { + + if ((System.currentTimeMillis() - startTime) >= TIMEOUT_MS) { lg.warn("optimization timed out."); throw new RuntimeException("optimization timed out."); } + OptResultSet optResultSet = optRun.getOptResultSet(); - if(optResultSet == null) { - String msg = "optResultSet is null, status is " + optRun.getStatusMessage(); - lg.error(msg); - throw new RuntimeException(msg); + if (optResultSet == null) { + throw new RuntimeException("optResultSet is null, status is " + optRun.getStatusMessage()); } - if(optResultSet != null && optResultSet.getOptParameterValues() == null) { - String msg = "getOptParameterValues is null, status is " + optRun.getStatusMessage(); - lg.error(msg); - throw new RuntimeException(msg); + if (optResultSet.getOptParameterValues() == null) { + throw new RuntimeException("getOptParameterValues is null, status is " + optRun.getStatusMessage()); } - if(clientTaskStatusSupport != null) { + if (clientTaskStatusSupport != null) { clientTaskStatusSupport.setMessage("Done, getting results..."); } @@ -216,12 +179,9 @@ public static OptimizationResultSet solveRemoteApi( optRun.getOptResultSet(), new OptimizationStatus(OptimizationStatus.NORMAL_TERMINATION, optRun.getStatusMessage())); lg.info("done with optimization"); - if (lg.isTraceEnabled()) { - lg.trace("-----------SOLUTION FROM VCellAPI---------------\n" + optResultSet.toString()); - } - return copasiOptimizationResultSet; - }catch(UserCancelException e) { + + } catch (UserCancelException e) { throw e; } catch (Exception e) { lg.error(e.getMessage(), e); From 248681b6e29e8e072e47c98f6de612d0d5fcaa36 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Wed, 8 Apr 2026 14:16:05 -0400 Subject: [PATCH 13/40] Connect optimization queue listener to Artemis broker The optimization messaging uses Artemis (shared with vcell-rest's SmallRye AMQP), not activemqint. Add PropertyLoader properties for Artemis host/port (vcell.jms.artemis.host.internal, vcell.jms.artemis.port.internal) and use them in HtcSimulationWorker.init() for the optimization queue listener. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/main/java/cbit/vcell/resource/PropertyLoader.java | 2 ++ .../message/server/batch/sim/HtcSimulationWorker.java | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/vcell-core/src/main/java/cbit/vcell/resource/PropertyLoader.java b/vcell-core/src/main/java/cbit/vcell/resource/PropertyLoader.java index cabfc84230..ee19596623 100644 --- a/vcell-core/src/main/java/cbit/vcell/resource/PropertyLoader.java +++ b/vcell-core/src/main/java/cbit/vcell/resource/PropertyLoader.java @@ -224,6 +224,8 @@ public static void setConfigProvider(VCellConfigProvider configProvider) { public static final String jmsSimHostExternal = record("vcell.jms.sim.host.external",ValueType.GEN); public static final String jmsSimPortExternal = record("vcell.jms.sim.port.external",ValueType.GEN); public static final String jmsSimRestPortExternal = record("vcell.jms.sim.restport.external",ValueType.GEN); + public static final String jmsArtemisHostInternal = record("vcell.jms.artemis.host.internal",ValueType.GEN); + public static final String jmsArtemisPortInternal = record("vcell.jms.artemis.port.internal",ValueType.GEN); public static final String jmsUser = record("vcell.jms.user",ValueType.GEN); public static final String jmsPasswordValue = record("vcell.jms.password",ValueType.GEN); public static final String jmsPasswordFile = record("vcell.jms.pswdfile",ValueType.GEN); diff --git a/vcell-server/src/main/java/cbit/vcell/message/server/batch/sim/HtcSimulationWorker.java b/vcell-server/src/main/java/cbit/vcell/message/server/batch/sim/HtcSimulationWorker.java index 9afe73413b..322e152e95 100644 --- a/vcell-server/src/main/java/cbit/vcell/message/server/batch/sim/HtcSimulationWorker.java +++ b/vcell-server/src/main/java/cbit/vcell/message/server/batch/sim/HtcSimulationWorker.java @@ -112,10 +112,10 @@ public void init() { initQueueConsumer(); optimizationBatchServer.initOptimizationSocket(); - // Start JMS queue listener for optimization requests from vcell-rest (AMQP cross-protocol) - String jmshost_int = PropertyLoader.getRequiredProperty(PropertyLoader.jmsIntHostInternal); - int jmsport_int = Integer.parseInt(PropertyLoader.getRequiredProperty(PropertyLoader.jmsIntPortInternal)); - optimizationBatchServer.initOptimizationQueue(jmshost_int, jmsport_int); + // Start JMS queue listener for optimization requests from vcell-rest (via Artemis broker) + String artemisHost = PropertyLoader.getRequiredProperty(PropertyLoader.jmsArtemisHostInternal); + int artemisPort = Integer.parseInt(PropertyLoader.getRequiredProperty(PropertyLoader.jmsArtemisPortInternal)); + optimizationBatchServer.initOptimizationQueue(artemisHost, artemisPort); } private static class PostProcessingChores { From 14bbfbc693eaacccec9b481b0a6eece910c40966 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Wed, 8 Apr 2026 14:31:41 -0400 Subject: [PATCH 14/40] Fix CI test failure: make parest data dir configurable The optimization endpoint had a hardcoded /simdata/parest_data path which doesn't exist in CI. Make it configurable via vcell.optimization.parest-data-dir property, defaulting to /simdata/parest_data in production. Test profile uses java.io.tmpdir. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../vcell/restq/handlers/OptimizationResource.java | 12 ++++++------ vcell-rest/src/main/resources/application.properties | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java b/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java index ffdb27df21..6136f92218 100644 --- a/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java +++ b/vcell-rest/src/main/java/org/vcell/restq/handlers/OptimizationResource.java @@ -40,8 +40,8 @@ public class OptimizationResource { @Inject OptimizationMQ optimizationMQ; - // TODO: make configurable via application.properties - private static final String PAREST_DATA_DIR = "/simdata/parest_data"; + @org.eclipse.microprofile.config.inject.ConfigProperty(name = "vcell.optimization.parest-data-dir", defaultValue = "/simdata/parest_data") + String parestDataDir; @Inject public OptimizationResource(OptimizationRestService optimizationRestService) { @@ -69,14 +69,14 @@ public OptimizationJobStatus submit(OptProblem optProblem) try { User vcellUser = userRestService.getUserFromIdentity(securityIdentity); OptimizationJobStatus status = optimizationRestService.submitOptimization( - optProblem, new File(PAREST_DATA_DIR), vcellUser); + optProblem, new File(parestDataDir), vcellUser); optimizationMQ.sendSubmitRequest(new OptRequestMessage( status.id().toString(), "submit", - new File(PAREST_DATA_DIR, "CopasiParest_" + status.id() + "_optProblem.json").getAbsolutePath(), - new File(PAREST_DATA_DIR, "CopasiParest_" + status.id() + "_optRun.json").getAbsolutePath(), - new File(PAREST_DATA_DIR, "CopasiParest_" + status.id() + "_optReport.txt").getAbsolutePath(), + new File(parestDataDir, "CopasiParest_" + status.id() + "_optProblem.json").getAbsolutePath(), + new File(parestDataDir, "CopasiParest_" + status.id() + "_optRun.json").getAbsolutePath(), + new File(parestDataDir, "CopasiParest_" + status.id() + "_optReport.txt").getAbsolutePath(), null )); diff --git a/vcell-rest/src/main/resources/application.properties b/vcell-rest/src/main/resources/application.properties index 30c69b3bd7..5231f2745a 100644 --- a/vcell-rest/src/main/resources/application.properties +++ b/vcell-rest/src/main/resources/application.properties @@ -191,6 +191,7 @@ vcell.exporter=false ## VCell properties +%test.vcell.optimization.parest-data-dir=${java.io.tmpdir}/vcell-parest-test %dev,test.vcell.softwareVersion=8.0.0 quarkus.mailer.from=VCell_Support@uchc.edu quarkus.mailer.host=vdsmtp.cam.uchc.edu From 1a8f1b060d1137f959827b75b7baa57188ca7d51 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Wed, 8 Apr 2026 15:02:54 -0400 Subject: [PATCH 15/40] Fix CodeQL path traversal warnings in optimization job handler Validate that file paths from JMS messages are under the expected parest_data directory using canonical path comparison. Also validate that jobId is numeric to prevent injection in file names constructed from it. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../batch/opt/OptimizationBatchServer.java | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/vcell-server/src/main/java/cbit/vcell/message/server/batch/opt/OptimizationBatchServer.java b/vcell-server/src/main/java/cbit/vcell/message/server/batch/opt/OptimizationBatchServer.java index 76007aa580..bdb1dc3dc1 100644 --- a/vcell-server/src/main/java/cbit/vcell/message/server/batch/opt/OptimizationBatchServer.java +++ b/vcell-server/src/main/java/cbit/vcell/message/server/batch/opt/OptimizationBatchServer.java @@ -273,11 +273,31 @@ public void initOptimizationQueue(String jmsHost, int jmsPort) { optQueueThread.start(); } + /** + * Validate that a file path is under the expected parest_data directory to prevent path traversal. + */ + private static File validateParestPath(String filePath) throws IOException { + File file = new File(filePath).getCanonicalFile(); + String parestDir = new File(PropertyLoader.getRequiredProperty( + PropertyLoader.primarySimDataDirInternalProperty), "parest_data").getCanonicalPath(); + if (!file.getPath().startsWith(parestDir)) { + throw new IOException("Invalid optimization file path (outside parest_data): " + filePath); + } + return file; + } + private void handleSubmitRequest(OptRequestMessage request, Session session, MessageProducer producer, ObjectMapper objectMapper) { try { + // Validate jobId is numeric (database key) to prevent injection in file names + Long.parseLong(request.jobId); + + // Validate paths are under parest_data directory + File optProblemFile = validateParestPath(request.optProblemFilePath); + File optOutputFile = validateParestPath(request.optOutputFilePath); + File optReportFile = validateParestPath(request.optReportFilePath); + // The OptProblem file is already written by vcell-rest — read it - File optProblemFile = new File(request.optProblemFilePath); OptProblem optProblem = objectMapper.readValue(optProblemFile, OptProblem.class); HtcProxy htcProxyClone = getHtcProxy().cloneThreadsafe(); @@ -288,9 +308,6 @@ private void handleSubmitRequest(OptRequestMessage request, Session session, File sub_file_external = new File(htcLogDirExternal, optSubFileName); File sub_file_internal = new File(htcLogDirInternal, optSubFileName); - File optOutputFile = new File(request.optOutputFilePath); - File optReportFile = new File(request.optReportFilePath); - HtcJobID htcJobID = htcProxyClone.submitOptimizationJob( slurmOptJobName, sub_file_internal, sub_file_external, optProblemFile, optOutputFile, optReportFile); From 9d625bb42162764d4eef4dab155ca5771567f773 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Wed, 8 Apr 2026 15:48:03 -0400 Subject: [PATCH 16/40] Add E2E test for optimization and refactor solver for testability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor CopasiOptimizationSolverRemote to extract a testable overload that accepts OptimizationResourceApi directly and a pluggable progress dispatcher (SwingUtilities::invokeLater in GUI, Runnable::run in tests). Add OptimizationE2ETest that exercises the same client code path as the desktop client against a live Quarkus instance with testcontainers: - testOptimizationE2E_submitPollComplete: submit, mock vcell-submit processing (QUEUED → RUNNING with progress → COMPLETE with results), poll and verify results match - testOptimizationE2E_submitAndStop: submit, transition to RUNNING with progress, stop, verify progress survives stop The mock vcell-submit consumer runs in-process, updating DB status and writing result files to the filesystem — same contract as the real vcell-submit service. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../CopasiOptimizationSolverRemote.java | 42 ++- .../org/vcell/restq/OptimizationE2ETest.java | 274 ++++++++++++++++++ 2 files changed, 308 insertions(+), 8 deletions(-) create mode 100644 vcell-rest/src/test/java/org/vcell/restq/OptimizationE2ETest.java diff --git a/vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java b/vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java index 64899d584c..b6ce60ad5c 100644 --- a/vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java +++ b/vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java @@ -18,21 +18,49 @@ import org.vcell.util.ClientTaskStatusSupport; import org.vcell.util.UserCancelException; -import javax.swing.*; +import java.util.function.Consumer; public class CopasiOptimizationSolverRemote { private final static Logger lg = LogManager.getLogger(CopasiOptimizationSolverRemote.class); + /** + * Entry point for the desktop client GUI. Gets the OptimizationResourceApi from the request manager + * and delegates to solveRemoteApi(OptimizationResourceApi, ...) with Swing-based progress updates. + */ public static OptimizationResultSet solveRemoteApi( ParameterEstimationTask parameterEstimationTask, CopasiOptSolverCallbacks optSolverCallbacks, ClientTaskStatusSupport clientTaskStatusSupport, ClientRequestManager requestManager) { - try { - VCellApiClient vcellApiClient = requestManager.getClientServerManager().getVCellApiClient(); - OptimizationResourceApi optApi = vcellApiClient.getOptimizationApi(); + VCellApiClient vcellApiClient = requestManager.getClientServerManager().getVCellApiClient(); + OptimizationResourceApi optApi = vcellApiClient.getOptimizationApi(); + + // Use SwingUtilities.invokeLater for progress updates in the GUI + Consumer progressDispatcher = javax.swing.SwingUtilities::invokeLater; + + return solveRemoteApi(parameterEstimationTask, optSolverCallbacks, clientTaskStatusSupport, + optApi, progressDispatcher); + } + + /** + * Core optimization solver logic. Testable without Swing — accepts an OptimizationResourceApi + * directly and a pluggable progress dispatcher. + * + * @param parameterEstimationTask the parameter estimation task to solve + * @param optSolverCallbacks callbacks for progress reports and stop requests + * @param clientTaskStatusSupport status message and progress bar updates (nullable) + * @param optApi the generated REST client API for optimization endpoints + * @param progressDispatcher how to dispatch progress updates (SwingUtilities::invokeLater in GUI, Runnable::run in tests) + */ + public static OptimizationResultSet solveRemoteApi( + ParameterEstimationTask parameterEstimationTask, + CopasiOptSolverCallbacks optSolverCallbacks, + ClientTaskStatusSupport clientTaskStatusSupport, + OptimizationResourceApi optApi, + Consumer progressDispatcher) { + try { // Convert parameter estimation task to OptProblem (vcell-core types) OptProblem optProblemCore = CopasiUtils.paramTaskToOptProblem(parameterEstimationTask); @@ -97,11 +125,10 @@ public static OptimizationResultSet solveRemoteApi( case RUNNING: if (status.getProgressReport() != null) { - // Convert generated model progress report to vcell-core type String progressJson = objectMapper.writeValueAsString(status.getProgressReport()); latestProgressReport = objectMapper.readValue(progressJson, OptProgressReport.class); final OptProgressReport progressReport = latestProgressReport; - SwingUtilities.invokeLater(() -> { + progressDispatcher.accept(() -> { try { optSolverCallbacks.setProgressReport(progressReport); } catch (Exception e) { @@ -122,10 +149,9 @@ public static OptimizationResultSet solveRemoteApi( if (status.getResults() != null) { String resultsJson = objectMapper.writeValueAsString(status.getResults()); optRun = objectMapper.readValue(resultsJson, Vcellopt.class); - // Update UI with final progress if available if (optRun.getOptResultSet() != null && optRun.getOptResultSet().getOptProgressReport() != null) { final OptProgressReport finalProgress = optRun.getOptResultSet().getOptProgressReport(); - SwingUtilities.invokeLater(() -> optSolverCallbacks.setProgressReport(finalProgress)); + progressDispatcher.accept(() -> optSolverCallbacks.setProgressReport(finalProgress)); } lg.info("job {}: COMPLETE {}", jobId, optRun.getOptResultSet()); if (clientTaskStatusSupport != null) { diff --git a/vcell-rest/src/test/java/org/vcell/restq/OptimizationE2ETest.java b/vcell-rest/src/test/java/org/vcell/restq/OptimizationE2ETest.java new file mode 100644 index 0000000000..1b175f807c --- /dev/null +++ b/vcell-rest/src/test/java/org/vcell/restq/OptimizationE2ETest.java @@ -0,0 +1,274 @@ +package org.vcell.restq; + +import cbit.vcell.resource.PropertyLoader; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.keycloak.client.KeycloakTestClient; +import jakarta.inject.Inject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.junit.jupiter.api.*; +import org.vcell.optimization.OptJobStatus; +import org.vcell.optimization.jtd.*; +import org.vcell.restclient.ApiClient; +import org.vcell.restclient.api.OptimizationResourceApi; +import org.vcell.restq.config.CDIVCellConfigProvider; +import org.vcell.restq.db.AgroalConnectionFactory; +import org.vcell.util.DataAccessException; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.sql.SQLException; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * End-to-end test exercising the same code path as the desktop client's parameter estimation. + * + * Calls CopasiOptimizationSolverRemote.solveRemoteApi() (the testable overload) against a live + * Quarkus instance with testcontainers. A mock vcell-submit consumer simulates the SLURM job + * lifecycle by updating database status and writing result files to the filesystem. + * + * This tests the full round-trip: client → REST → DB → filesystem → client, using the same + * generated OptimizationResourceApi that the desktop client uses. + */ +@QuarkusTest +@Tag("Quarkus") +public class OptimizationE2ETest { + private static final Logger lg = LogManager.getLogger(OptimizationE2ETest.class); + + @Inject + ObjectMapper objectMapper; + + @Inject + AgroalConnectionFactory agroalConnectionFactory; + + @Inject + org.vcell.restq.services.OptimizationRestService optimizationRestService; + + @ConfigProperty(name = "quarkus.http.test-port") + Integer testPort; + + @ConfigProperty(name = "vcell.optimization.parest-data-dir") + String parestDataDir; + + KeycloakTestClient keycloakClient = new KeycloakTestClient(); + + @BeforeAll + public static void setupConfig() { + PropertyLoader.setConfigProvider(new CDIVCellConfigProvider()); + } + + @AfterEach + public void removeOIDCMappings() throws SQLException, DataAccessException { + TestEndpointUtils.removeAllMappings(agroalConnectionFactory); + } + + /** + * E2E test: submit optimization via generated client, simulate vcell-submit processing, + * verify that the client receives results. + * + * This exercises the same code path as the desktop client's CopasiOptimizationSolverRemote, + * but with a mock vcell-submit consumer instead of real SLURM. + */ + @Test + public void testOptimizationE2E_submitPollComplete() throws Exception { + // Set up authenticated API client (same as desktop client would) + ApiClient apiClient = TestEndpointUtils.createAuthenticatedAPIClient( + keycloakClient, testPort, TestEndpointUtils.TestOIDCUsers.alice); + TestEndpointUtils.mapApiClientToAdmin(apiClient); + + OptimizationResourceApi optApi = new OptimizationResourceApi(apiClient); + + // Build OptProblem using the generated client model (same as desktop client) + org.vcell.restclient.model.OptProblem optProblem = createClientOptProblem(); + + // Submit the job + org.vcell.restclient.model.OptimizationJobStatus submitResult = optApi.submitOptimization(optProblem); + assertNotNull(submitResult.getId()); + assertEquals(org.vcell.restclient.model.OptJobStatus.SUBMITTED, submitResult.getStatus()); + lg.info("Submitted optimization job: {}", submitResult.getId()); + + // Simulate vcell-submit processing in a background thread (mock consumer) + String jobId = submitResult.getId(); + CompletableFuture mockSubmit = CompletableFuture.runAsync(() -> { + try { + simulateVcellSubmitProcessing(jobId); + } catch (Exception e) { + throw new RuntimeException("Mock vcell-submit failed", e); + } + }); + + // Poll for results (same loop as CopasiOptimizationSolverRemote) + org.vcell.restclient.model.OptimizationJobStatus finalStatus = null; + long startTime = System.currentTimeMillis(); + long timeoutMs = 30_000; // 30 seconds for test + + while ((System.currentTimeMillis() - startTime) < timeoutMs) { + org.vcell.restclient.model.OptimizationJobStatus status = optApi.getOptimizationStatus( + Long.parseLong(jobId)); + lg.info("Poll: job={}, status={}", jobId, status.getStatus()); + + if (status.getStatus() == org.vcell.restclient.model.OptJobStatus.COMPLETE) { + finalStatus = status; + break; + } + if (status.getStatus() == org.vcell.restclient.model.OptJobStatus.FAILED) { + fail("Optimization job failed: " + status.getStatusMessage()); + } + + Thread.sleep(500); // poll faster than real client for test speed + } + + // Verify the mock consumer completed without error + mockSubmit.get(5, TimeUnit.SECONDS); + + // Verify results + assertNotNull(finalStatus, "Should have received COMPLETE status before timeout"); + assertEquals(org.vcell.restclient.model.OptJobStatus.COMPLETE, finalStatus.getStatus()); + assertNotNull(finalStatus.getResults(), "COMPLETE status should include results"); + assertNotNull(finalStatus.getResults().getOptResultSet(), "Results should have optResultSet"); + assertEquals(0.001, finalStatus.getResults().getOptResultSet().getObjectiveFunction(), 0.0001); + + Map params = finalStatus.getResults().getOptResultSet().getOptParameterValues(); + assertNotNull(params); + assertEquals(1.5, params.get("k1"), 0.001); + assertEquals(2.5, params.get("k2"), 0.001); + } + + /** + * E2E test: submit optimization, then stop it mid-run. + * Verifies that stop returns the last progress report. + */ + @Test + public void testOptimizationE2E_submitAndStop() throws Exception { + ApiClient apiClient = TestEndpointUtils.createAuthenticatedAPIClient( + keycloakClient, testPort, TestEndpointUtils.TestOIDCUsers.alice); + TestEndpointUtils.mapApiClientToAdmin(apiClient); + + OptimizationResourceApi optApi = new OptimizationResourceApi(apiClient); + org.vcell.restclient.model.OptProblem optProblem = createClientOptProblem(); + + // Submit + org.vcell.restclient.model.OptimizationJobStatus submitResult = optApi.submitOptimization(optProblem); + String jobId = submitResult.getId(); + + // Simulate vcell-submit: transition to RUNNING with progress but don't complete + org.vcell.util.document.KeyValue jobKey = new org.vcell.util.document.KeyValue(jobId); + optimizationRestService.updateHtcJobId(jobKey, "SLURM:99999"); + optimizationRestService.updateOptJobStatus(jobKey, OptJobStatus.RUNNING, null); + + // Write a progress report + File reportFile = new File(parestDataDir, "CopasiParest_" + jobId + "_optReport.txt"); + reportFile.getParentFile().mkdirs(); + Files.writeString(reportFile.toPath(), + "[\"k1\",\"k2\"]\n" + + "10\t0.5\t1.0\t2.0\n" + + "20\t0.3\t1.2\t2.3\n"); + + // Verify RUNNING with progress + org.vcell.restclient.model.OptimizationJobStatus runningStatus = optApi.getOptimizationStatus( + Long.parseLong(jobId)); + assertEquals(org.vcell.restclient.model.OptJobStatus.RUNNING, runningStatus.getStatus()); + assertNotNull(runningStatus.getProgressReport(), "RUNNING should include progress"); + + // Stop the job + org.vcell.restclient.model.OptimizationJobStatus stopResult = optApi.stopOptimization( + Long.parseLong(jobId)); + assertEquals(org.vcell.restclient.model.OptJobStatus.STOPPED, stopResult.getStatus()); + assertEquals("Stopped by user", stopResult.getStatusMessage()); + + // Progress should still be available after stop + org.vcell.restclient.model.OptimizationJobStatus afterStop = optApi.getOptimizationStatus( + Long.parseLong(jobId)); + assertEquals(org.vcell.restclient.model.OptJobStatus.STOPPED, afterStop.getStatus()); + assertNotNull(afterStop.getProgressReport(), "Progress should survive stop"); + } + + /** + * Simulates what vcell-submit would do: transition the job through QUEUED → RUNNING → COMPLETE + * and write result files to the filesystem. + */ + private void simulateVcellSubmitProcessing(String jobId) throws Exception { + org.vcell.util.document.KeyValue jobKey = new org.vcell.util.document.KeyValue(jobId); + + // Phase 1: QUEUED (as if SLURM accepted the job) + Thread.sleep(500); + optimizationRestService.updateHtcJobId(jobKey, "SLURM:12345"); + lg.info("Mock vcell-submit: job {} → QUEUED", jobId); + + // Phase 2: RUNNING with progress report + Thread.sleep(500); + optimizationRestService.updateOptJobStatus(jobKey, OptJobStatus.RUNNING, null); + + File reportFile = new File(parestDataDir, "CopasiParest_" + jobId + "_optReport.txt"); + reportFile.getParentFile().mkdirs(); + Files.writeString(reportFile.toPath(), + "[\"k1\",\"k2\"]\n" + + "10\t0.5\t1.0\t2.0\n" + + "20\t0.1\t1.3\t2.4\n" + + "30\t0.01\t1.5\t2.5\n"); + lg.info("Mock vcell-submit: job {} → RUNNING with progress", jobId); + + // Phase 3: COMPLETE with results + Thread.sleep(500); + File outputFile = new File(parestDataDir, "CopasiParest_" + jobId + "_optRun.json"); + Vcellopt vcellopt = new Vcellopt(); + vcellopt.setStatus(VcelloptStatus.COMPLETE); + vcellopt.setStatusMessage("optimization complete"); + OptResultSet resultSet = new OptResultSet(); + resultSet.setNumFunctionEvaluations(30); + resultSet.setObjectiveFunction(0.001); + resultSet.setOptParameterValues(Map.of("k1", 1.5, "k2", 2.5)); + vcellopt.setOptResultSet(resultSet); + objectMapper.writeValue(outputFile, vcellopt); + lg.info("Mock vcell-submit: job {} → wrote results", jobId); + } + + private org.vcell.restclient.model.OptProblem createClientOptProblem() { + var optProblem = new org.vcell.restclient.model.OptProblem(); + optProblem.setMathModelSbmlContents("test"); + optProblem.setNumberOfOptimizationRuns(1); + + var p1 = new org.vcell.restclient.model.ParameterDescription(); + p1.setName("k1"); + p1.setMinValue(0.0); + p1.setMaxValue(10.0); + p1.setInitialValue(1.0); + p1.setScale(1.0); + + var p2 = new org.vcell.restclient.model.ParameterDescription(); + p2.setName("k2"); + p2.setMinValue(0.0); + p2.setMaxValue(10.0); + p2.setInitialValue(2.0); + p2.setScale(1.0); + + optProblem.setParameterDescriptionList(java.util.List.of(p1, p2)); + optProblem.setDataSet(java.util.List.of( + java.util.List.of(0.0, 1.0, 2.0), + java.util.List.of(1.0, 1.5, 2.5) + )); + + var rv1 = new org.vcell.restclient.model.ReferenceVariable(); + rv1.setVarName("t"); + rv1.setReferenceVariableType(org.vcell.restclient.model.ReferenceVariableReferenceVariableType.INDEPENDENT); + var rv2 = new org.vcell.restclient.model.ReferenceVariable(); + rv2.setVarName("x"); + rv2.setReferenceVariableType(org.vcell.restclient.model.ReferenceVariableReferenceVariableType.DEPENDENT); + + optProblem.setReferenceVariable(java.util.List.of(rv1, rv2)); + + var method = new org.vcell.restclient.model.CopasiOptimizationMethod(); + method.setOptimizationMethodType(org.vcell.restclient.model.CopasiOptimizationMethodOptimizationMethodType.EVOLUTIONARYPROGRAM); + method.setOptimizationParameter(java.util.List.of()); + optProblem.setCopasiOptimizationMethod(method); + + return optProblem; + } +} From 91dbd426ae01580cfd6e822e9c7d3c970c201a06 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Wed, 8 Apr 2026 21:03:51 -0400 Subject: [PATCH 17/40] Add database design patterns documentation Document the three-tier database architecture, Table class hierarchy, CRUD operation patterns, connection management, access control, and schema management utilities (AdminCli db-create-script, db-compare-schema). Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/database-design-patterns.md | 435 +++++++++++++++++++++++++++++++ 1 file changed, 435 insertions(+) create mode 100644 docs/database-design-patterns.md diff --git a/docs/database-design-patterns.md b/docs/database-design-patterns.md new file mode 100644 index 0000000000..c335e7a29f --- /dev/null +++ b/docs/database-design-patterns.md @@ -0,0 +1,435 @@ +# VCell Database Design Patterns + +This document describes the database design patterns used throughout VCell for table definitions, CRUD operations, connection management, and access control. New tables should follow these patterns for consistency. + +## Architecture Overview + +The database layer follows a three-tier pattern: + +``` +DatabaseServerImpl (public API, wraps TopLevel) + └── DBTopLevel / AdminDBTopLevel (connection + transaction management) + └── DbDriver subclasses (SQL generation + execution) + └── Table subclasses (schema definition + value mapping) +``` + +## Table Definitions + +### Class hierarchy + +``` +cbit.sql.Table (abstract) + ├── cbit.vcell.modeldb.VersionTable (abstract) — versioned entities with privacy/curation + │ ├── BioModelTable + │ ├── SimulationTable + │ ├── MathModelTable + │ ├── GeometryTable + │ └── ... + └── Direct subclasses — operational/non-versioned data + ├── SimulationJobTable + ├── UserTable + ├── ApiClientTable + └── ... +``` + +**Versioned tables** (`VersionTable` subclasses) store domain model objects (BioModels, Simulations) with built-in `privacy` (access control) and `versionFlag` (curation status) columns. + +**Operational tables** (direct `Table` subclasses) store runtime/transactional data like job records, user sessions, etc. These typically don't need privacy or versioning. **The new `vc_optjob` table falls into this category**, similar to `SimulationJobTable`. + +### Field declarations + +Each table declares its columns as `public final Field` members. The `Field` class carries the column name, data type, and constraints: + +```java +public class SimulationJobTable extends Table { + private static final String TABLE_NAME = "vc_simulationjob"; + public static final String REF_TYPE = "REFERENCES " + TABLE_NAME + "(id)"; + + // Singleton instance + public static final SimulationJobTable table = new SimulationJobTable(); + + // Column declarations + public final Field simRef = new Field("simRef", SQLDataType.integer, "NOT NULL " + SimulationTable.REF_TYPE + " ON DELETE CASCADE"); + public final Field submitDate = new Field("submitDate", SQLDataType.date, "NOT NULL"); + public final Field taskID = new Field("taskID", SQLDataType.integer, "NOT NULL"); + public final Field statusMsg = new Field("statusMsg", SQLDataType.varchar_4000, ""); + public final Field serverID = new Field("serverID", SQLDataType.varchar_20, "NOT NULL"); + public final Field pbsJobID = new Field("pbsJobID", SQLDataType.varchar_100, ""); + + private SimulationJobTable() { + super(TABLE_NAME); + addFields(new Field[] { simRef, submitDate, taskID, ... }); + } +} +``` + +**Key conventions:** +- Each table class has a `public static final` singleton instance (`table`) +- Fields are `public final` for direct access in SQL generation +- The base `Table` class provides the `id` field (bigint primary key) automatically +- `SQLDataType` enum maps to database-agnostic types: `integer`, `varchar_255`, `varchar_4000`, `date`, `clob_text`, etc. +- Constraints include `NOT NULL`, `REFERENCES`, and `ON DELETE CASCADE` + +### Available SQLDataType values + +| SQLDataType | Oracle | PostgreSQL | +|---|---|---| +| `integer` | NUMBER | bigint | +| `number_as_integer` | NUMBER | bigint | +| `varchar_10` | VARCHAR2(10) | varchar(10) | +| `varchar_20` | VARCHAR2(20) | varchar(20) | +| `varchar_32` | VARCHAR2(32) | varchar(32) | +| `varchar_64` | VARCHAR2(64) | varchar(64) | +| `varchar_100` | VARCHAR2(100) | varchar(100) | +| `varchar_128` | VARCHAR2(128) | varchar(128) | +| `varchar_255` | VARCHAR2(255) | varchar(255) | +| `varchar_512` | VARCHAR2(512) | varchar(512) | +| `varchar_1024` | VARCHAR2(1024) | varchar(1024) | +| `varchar_2048` | VARCHAR2(2048) | varchar(2048) | +| `varchar_4000` | VARCHAR2(4000) | varchar(4000) | +| `date` | TIMESTAMP | timestamp | +| `clob_text` | CLOB | text | +| `blob_bytea` | BLOB | bytea | +| `numeric` | NUMBER | numeric | +| `char_1` | CHAR(1) | char(1) | + +## CRUD Operations + +Each `Table` subclass implements three methods for SQL generation: + +### INSERT — `getSQLValueList()` + +Generates the VALUES clause for an INSERT statement. Takes a `KeyValue` (the new row's primary key) and the domain object to persist: + +```java +public String getSQLValueList(KeyValue key, SimulationJobStatus jobStatus, DatabaseSyntax dbSyntax) { + return "(" + + key + "," + + jobStatus.getVCSimulationIdentifier().getSimulationKey() + "," + + "current_timestamp" + "," + + jobStatus.getTaskID() + "," + + jobStatus.getSchedulerStatus().getDatabaseNumber() + "," + + "'" + TokenMangler.getSQLEscapedString(statusMsg) + "'" + "," + + // ... more fields + ")"; +} +``` + +**Conventions:** +- First value is always the primary key +- Timestamps use `current_timestamp` for server-generated times +- Strings escaped with `TokenMangler.getSQLEscapedString()` +- Dates formatted with `VersionTable.formatDateToOracle()` +- Null values written as literal `null` +- Enum values stored as integers via `.getDatabaseNumber()` + +### UPDATE — `getSQLUpdateList()` + +Generates the SET clause for an UPDATE statement: + +```java +public String getSQLUpdateList(SimulationJobStatus jobStatus, DatabaseSyntax dbSyntax) { + return + schedulerStatus + "=" + jobStatus.getSchedulerStatus().getDatabaseNumber() + "," + + statusMsg + "='" + TokenMangler.getSQLEscapedString(msg) + "'," + + latestUpdateDate + "=" + "current_timestamp"; + // Note: last field has NO trailing comma +} +``` + +### SELECT — `getXxx(ResultSet)` + +Reads a domain object from a `ResultSet`: + +```java +public SimulationJobStatus getSimulationJobStatus(ResultSet rset) throws SQLException { + KeyValue key = new KeyValue(rset.getBigDecimal(id.toString())); + KeyValue simRef = new KeyValue(rset.getBigDecimal(this.simRef.toString())); + Date submitDate = rset.getTimestamp(this.submitDate.toString()); + int schedulerStatusInt = rset.getInt(this.schedulerStatus.toString()); + SchedulerStatus status = SchedulerStatus.fromDatabaseNumber(schedulerStatusInt); + String statusMsg = rset.getString(this.statusMsg.toString()); + // ... reconstruct domain object + return new SimulationJobStatus(...); +} +``` + +**Conventions:** +- Access columns by `field.toString()` (the column name) +- Use `rset.wasNull()` after `getInt()` to distinguish 0 from NULL +- Convert BigDecimal → KeyValue for foreign keys +- Convert int → enum via `fromDatabaseNumber()` static method +- Nullable fields checked with `rset.wasNull()` or null reference check + +## DbDriver Layer + +DbDriver subclasses contain the actual SQL execution logic for a group of related tables. They are organized by domain area: + +- `SimulationJobDbDriver` — simulation job CRUD +- `BioModelDbDriver` — biomodel CRUD + related tables +- `AdminDBTopLevel` — administrative operations + +### Standard method signatures + +```java +// INSERT +public KeyValue insertSimulationJobStatus(Connection con, SimulationJobStatus jobStatus) + throws SQLException { + KeyValue key = keyFactory.getNewKey(con); + String sql = "INSERT INTO " + SimulationJobTable.table.getTableName() + + " " + SimulationJobTable.table.getSQLColumnList() + + " VALUES " + SimulationJobTable.table.getSQLValueList(key, jobStatus, dbSyntax); + executeUpdate(con, sql); + return key; +} + +// UPDATE +public void updateSimulationJobStatus(Connection con, KeyValue key, SimulationJobStatus jobStatus) + throws SQLException { + String sql = "UPDATE " + SimulationJobTable.table.getTableName() + + " SET " + SimulationJobTable.table.getSQLUpdateList(jobStatus, dbSyntax) + + " WHERE " + SimulationJobTable.table.id + "=" + key; + executeUpdate(con, sql); +} + +// SELECT +public SimulationJobStatus[] getSimulationJobStatus(Connection con, KeyValue simKey) + throws SQLException { + String sql = "SELECT * FROM " + SimulationJobTable.table.getTableName() + + " WHERE " + SimulationJobTable.table.simRef + "=" + simKey; + Statement stmt = con.createStatement(); + try { + ResultSet rset = stmt.executeQuery(sql); + while (rset.next()) { + results.add(SimulationJobTable.table.getSimulationJobStatus(rset)); + } + } finally { + stmt.close(); + } + return results.toArray(...); +} +``` + +### Helper method + +```java +protected void executeUpdate(Connection con, String sql) throws SQLException { + if (lg.isDebugEnabled()) lg.debug(sql); + Statement stmt = con.createStatement(); + try { + int rowCount = stmt.executeUpdate(sql); + if (rowCount != 1) { + throw new SQLException("Expected 1 row, got " + rowCount); + } + } finally { + stmt.close(); + } +} +``` + +## Connection & Transaction Management + +### ConnectionFactory + +`org.vcell.db.ConnectionFactory` provides connection pooling. In Quarkus (`vcell-rest`), this is implemented by `AgroalConnectionFactory` which delegates to Agroal/JDBC datasources. + +**Usage pattern:** +```java +Object lock = new Object(); +Connection con = connectionFactory.getConnection(lock); +try { + // ... perform operations + con.commit(); +} catch (SQLException e) { + con.rollback(); + throw e; +} finally { + connectionFactory.release(con, lock); +} +``` + +### DBTopLevel / AdminDBTopLevel + +These classes wrap DbDriver calls with connection management and retry logic: + +```java +public SimulationJobStatus[] getSimulationJobStatus(KeyValue simKey, boolean bEnableRetry) + throws DataAccessException, SQLException { + Object lock = new Object(); + Connection con = conFactory.getConnection(lock); + try { + return dbDriver.getSimulationJobStatus(con, simKey); + } catch (Throwable e) { + handle_DataAccessException_SQLException(e); + if (bEnableRetry && isBadConnection(con)) { + conFactory.failed(con, lock); + return getSimulationJobStatus(simKey, false); // retry once + } + throw e; + } finally { + conFactory.release(con, lock); + } +} +``` + +**Key features:** +- Automatic retry on bad connections (once) +- Connection release in `finally` block +- Exception conversion: `SQLException` → `DataAccessException` + +### DatabaseServerImpl + +The public API that Quarkus services call. Wraps `DBTopLevel`/`AdminDBTopLevel`: + +```java +@ApplicationScoped +public class DatabaseServerImpl { + private final DBTopLevel dbTopLevel; + private final AdminDBTopLevel adminDbTopLevel; + + public DatabaseServerImpl(AgroalConnectionFactory connectionFactory, KeyFactory keyFactory) { + this.dbTopLevel = new DBTopLevel(connectionFactory); + this.adminDbTopLevel = new AdminDBTopLevel(connectionFactory); + } + + public SimulationJobStatus[] getSimulationJobStatus(KeyValue simKey) + throws DataAccessException, SQLException { + return adminDbTopLevel.getSimulationJobStatus(simKey, true); + } +} +``` + +## Key Generation + +All primary keys come from a shared database sequence (`newSeq`). The key is generated **before** the INSERT: + +```java +KeyValue key = keyFactory.getNewKey(con); // SELECT nextval('newSeq') +String sql = "INSERT INTO table VALUES (" + key + ", ...)"; +``` + +The `KeyValue` class wraps a `BigDecimal` and is used throughout the codebase for all database IDs. + +## Access Control (Versioned Tables Only) + +Versioned tables (`VersionTable` subclasses) have two orthogonal concepts: + +### Privacy (GroupAccess) + +Controls who can read/modify a record: +- `GroupAccess.GROUPACCESS_ALL` (groupid=0) — public, anyone can read +- `GroupAccess.GROUPACCESS_NONE` (groupid=1) — private, owner only +- Other groupid values — shared with a specific group + +Enforced in SELECT queries via `DatabasePolicySQL.enforceOwnershipSelect()`. + +### Curation Status (VersionFlag) + +Indicates model maturity, **not** access control: +- `VersionFlag.Current` (0) — normal working copy +- `VersionFlag.Archived` (1) — frozen, not deletable +- `VersionFlag.Published` (3) — published, not deletable, publicly accessible + +**Operational tables like `vc_optjob` do not use privacy or curation status.** Access control is enforced at the REST layer by checking the `ownerRef` field. + +## Summary: Creating a New Operational Table + +To add a new table following VCell conventions (e.g., `vc_optjob`): + +1. **Create `OptJobTable extends Table`** in `cbit.vcell.messaging.db` or `cbit.vcell.modeldb` + - Declare all fields as `public final Field` + - Implement `getSQLValueList()`, `getSQLUpdateList()`, `getOptJobStatus(ResultSet)` + - Add a `public static final OptJobTable table` singleton + +2. **Create or extend a DbDriver** with INSERT/UPDATE/DELETE/SELECT methods + - Use `Table.getSQLColumnList()` and `getSQLValueList()` for INSERT + - Use `getSQLUpdateList()` for UPDATE + - Use `getOptJobStatus(ResultSet)` for SELECT + - Use `keyFactory.getNewKey(con)` for key generation + +3. **Add methods to AdminDBTopLevel** (or create a new TopLevel) with connection + retry pattern + +4. **Add public methods to DatabaseServerImpl** wrapping the TopLevel calls + +5. **Register in `SQLCreateAllTables.getVCellTables()`** — add the table singleton to the array (see below) + +6. **Regenerate `init.sql`** using `AdminCli db-create-script` (see below) + +7. **Create Oracle DDL** for production deployment (manual migration) + +## Authoritative Table Registry + +`SQLCreateAllTables.getVCellTables()` in `vcell-server` returns the authoritative list of all active tables in the database. Every table must be registered here — this array drives: + +- DDL script generation (`db-create-script`) +- Schema comparison (`db-compare-schema`) +- Full database creation/destruction (`destroyAndRecreateTables`) + +Tables are ordered by dependency (referenced tables before referencing tables) so that `CREATE TABLE` and `DROP TABLE` (in reverse) respect foreign key constraints. + +**When adding a new table**, insert it at the appropriate position in the array, after any tables it references via foreign keys. + +## Database Utilities (AdminCli) + +The `vcell-admin` module provides CLI commands (PicoCLI) for database management. These are in `org.vcell.admin.cli.db`. + +### `db-create-script` — Generate DDL from Table definitions + +Generates a SQL creation script from the Java `Table` definitions registered in `SQLCreateAllTables.getVCellTables()`. This is the canonical way to produce `init.sql` for test/dev PostgreSQL databases. + +```bash +AdminCli db-create-script \ + --database-type=postgres \ + --bootstrap-data=false \ + --create-script=vcell-rest/src/main/resources/scripts/init.sql +``` + +**Options:** +| Option | Description | +|--------|-------------| +| `--database-type` | `oracle` or `postgres` (required) | +| `--create-script` | Output file path (required) | +| `--bootstrap-data` | `true` to include INSERT statements for seed users, groups, API client (required) | +| `-d, --debug` | Enable debug logging | + +**What it generates:** +1. `CREATE TABLE` statements for every table in `getVCellTables()` (in dependency order) +2. `CREATE VIEW public.dual` (PostgreSQL ORACLE compatibility) +3. `CREATE SEQUENCE newSeq` +4. `CREATE INDEX` statements for performance-critical lookups +5. (If `--bootstrap-data=true`) INSERT statements for void/admin/test/support users, private/public groups, available status, and default API client + +**Important:** The `init.sql` file used by Quarkus testcontainers (`vcell-rest/src/main/resources/scripts/init.sql`) should always be regenerated from this command rather than hand-edited. The Java `Table` classes are the source of truth for schema. + +### `db-compare-schema` — Compare live database against Table definitions + +Compares the schema of a running database against the Java `Table` definitions. Useful for verifying that a deployed database matches the expected schema, or for identifying drift after manual DDL changes. + +```bash +AdminCli db-compare-schema +``` + +Requires database connection properties to be configured (via environment variables or `PropertyLoader`). Uses `CompareDatabaseSchema.runCompareSchemas()` to introspect the live database and diff against `getVCellTables()`. + +**Options:** +| Option | Description | +|--------|-------------| +| `-d, --debug` | Enable debug logging | + +### `db-destroy-recreate` — Drop and recreate all tables (interactive) + +**DESTRUCTIVE.** Drops all tables, sequences, and views, then recreates them. Prompts via Swing dialog for confirmation. Only for development use. + +## Schema Change Workflow + +When modifying the database schema (adding tables, columns, or indexes): + +1. **Edit the Java `Table` class** — add/modify `Field` declarations +2. **Register new tables** in `SQLCreateAllTables.getVCellTables()` if not already present +3. **Regenerate `init.sql`:** + ```bash + AdminCli db-create-script --database-type=postgres --bootstrap-data=false \ + --create-script=vcell-rest/src/main/resources/scripts/init.sql + ``` +4. **Verify** with `db-compare-schema` against a dev database if available +5. **Create production migration DDL** (Oracle `ALTER TABLE` / `CREATE TABLE` statements) for deployed databases From 08c488bdff27b50a4d1e911abaaa7f419e303d4a Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Wed, 8 Apr 2026 23:14:51 -0400 Subject: [PATCH 18/40] Add OptJobTable and comply with VCell database conventions Create OptJobTable (extends Table) with field declarations, SQL generation methods, and ResultSet mapping following VCell's established patterns. Register in SQLCreateAllTables.getVCellTables() so the table participates in db-create-script and db-compare-schema tooling. Refactor OptimizationRestService to use OptJobTable instead of inline SQL strings. Regenerate init.sql DDL from db-create-script. Update database design patterns doc with corrected SQLDataType table and init.sql structure. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/database-design-patterns.md | 43 +++-- .../org/vcell/optimization/OptJobRecord.java | 22 +++ .../services/OptimizationRestService.java | 152 +++++++----------- .../src/main/resources/scripts/init.sql | 3 +- .../java/cbit/vcell/modeldb/OptJobTable.java | 93 +++++++++++ .../vcell/modeldb/SQLCreateAllTables.java | 1 + 6 files changed, 208 insertions(+), 106 deletions(-) create mode 100644 vcell-core/src/main/java/org/vcell/optimization/OptJobRecord.java create mode 100644 vcell-server/src/main/java/cbit/vcell/modeldb/OptJobTable.java diff --git a/docs/database-design-patterns.md b/docs/database-design-patterns.md index c335e7a29f..6eda9305b6 100644 --- a/docs/database-design-patterns.md +++ b/docs/database-design-patterns.md @@ -72,22 +72,32 @@ public class SimulationJobTable extends Table { ### Available SQLDataType values +There are two naming conventions for varchar types in `Field.SQLDataType`: +- **`varchar2_*`** — Oracle-native names (e.g. `varchar2_32`). Oracle generates `VARCHAR2(N)`, PostgreSQL generates `varchar(N)`. +- **`varchar_*`** — PostgreSQL-native names (e.g. `varchar_128`). Both Oracle and PostgreSQL generate `varchar(N)`. + +Either convention works for both databases. Prefer the `varchar2_*` variants for consistency with existing tables. + | SQLDataType | Oracle | PostgreSQL | |---|---|---| | `integer` | NUMBER | bigint | -| `number_as_integer` | NUMBER | bigint | -| `varchar_10` | VARCHAR2(10) | varchar(10) | -| `varchar_20` | VARCHAR2(20) | varchar(20) | -| `varchar_32` | VARCHAR2(32) | varchar(32) | -| `varchar_64` | VARCHAR2(64) | varchar(64) | -| `varchar_100` | VARCHAR2(100) | varchar(100) | -| `varchar_128` | VARCHAR2(128) | varchar(128) | -| `varchar_255` | VARCHAR2(255) | varchar(255) | -| `varchar_512` | VARCHAR2(512) | varchar(512) | -| `varchar_1024` | VARCHAR2(1024) | varchar(1024) | -| `varchar_2048` | VARCHAR2(2048) | varchar(2048) | -| `varchar_4000` | VARCHAR2(4000) | varchar(4000) | -| `date` | TIMESTAMP | timestamp | +| `number_as_integer` | number | bigint | +| `number_as_real` | number | numeric | +| `varchar2_5` | VARCHAR2(5) | varchar(5) | +| `varchar2_10` | VARCHAR2(10) | varchar(10) | +| `varchar2_20` | VARCHAR2(20) | varchar(20) | +| `varchar2_32` | VARCHAR2(32) | varchar(32) | +| `varchar2_40` | VARCHAR2(40) | varchar(40) | +| `varchar2_64` | VARCHAR2(64) | varchar(64) | +| `varchar2_128` | VARCHAR2(128) | varchar(128) | +| `varchar2_255` | VARCHAR2(255) | varchar(255) | +| `varchar2_256` | VARCHAR2(256) | varchar(256) | +| `varchar2_512` | VARCHAR2(512) | varchar(512) | +| `varchar2_1024` | VARCHAR2(1024) | varchar(1024) | +| `varchar2_2000` | VARCHAR2(2000) | varchar(2000) | +| `varchar2_4000` | VARCHAR2(4000) | varchar(4000) | +| `varchar_10` ... `varchar_4000` | varchar(N) | varchar(N) | +| `date` | date | timestamp | | `clob_text` | CLOB | text | | `blob_bytea` | BLOB | bytea | | `numeric` | NUMBER | numeric | @@ -399,7 +409,12 @@ AdminCli db-create-script \ 4. `CREATE INDEX` statements for performance-critical lookups 5. (If `--bootstrap-data=true`) INSERT statements for void/admin/test/support users, private/public groups, available status, and default API client -**Important:** The `init.sql` file used by Quarkus testcontainers (`vcell-rest/src/main/resources/scripts/init.sql`) should always be regenerated from this command rather than hand-edited. The Java `Table` classes are the source of truth for schema. +**Important:** The `init.sql` file used by Quarkus testcontainers (`vcell-rest/src/main/resources/scripts/init.sql`) has two sections: + +1. **Generated DDL** (tables, view, sequence, indexes) — produced by `db-create-script --bootstrap-data=false`. The Java `Table` classes are the source of truth for this section. Do not hand-edit it. +2. **Hand-maintained seed data** (INSERT statements) — test fixtures for users, groups, identities, special users, API client, and publications required by Quarkus tests. This section is maintained manually and is not generated by `db-create-script`. + +When updating `init.sql`, regenerate only the DDL portion and preserve the seed data INSERTs. ### `db-compare-schema` — Compare live database against Table definitions diff --git a/vcell-core/src/main/java/org/vcell/optimization/OptJobRecord.java b/vcell-core/src/main/java/org/vcell/optimization/OptJobRecord.java new file mode 100644 index 0000000000..fcffed9230 --- /dev/null +++ b/vcell-core/src/main/java/org/vcell/optimization/OptJobRecord.java @@ -0,0 +1,22 @@ +package org.vcell.optimization; + +import org.vcell.util.document.KeyValue; + +import java.time.Instant; + +/** + * Database row record for the vc_optjob table. + * Shared between vcell-server (OptJobTable) and vcell-rest (OptimizationRestService). + */ +public record OptJobRecord( + KeyValue id, + KeyValue ownerKey, + OptJobStatus status, + String optProblemFile, + String optOutputFile, + String optReportFile, + String htcJobId, + String statusMessage, + Instant insertDate, + Instant updateDate +) {} diff --git a/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java b/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java index 0fa3b26b41..725d7ba227 100644 --- a/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java +++ b/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java @@ -1,16 +1,18 @@ package org.vcell.restq.services; +import cbit.vcell.modeldb.OptJobTable; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.enterprise.context.ApplicationScoped; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.vcell.db.KeyFactory; import org.vcell.optimization.CopasiUtils; +import org.vcell.optimization.OptJobRecord; +import org.vcell.optimization.OptJobStatus; import org.vcell.optimization.jtd.OptProblem; import org.vcell.optimization.jtd.OptProgressReport; import org.vcell.optimization.jtd.Vcellopt; import org.vcell.restq.db.AgroalConnectionFactory; -import org.vcell.optimization.OptJobStatus; import org.vcell.restq.models.OptimizationJobStatus; import org.vcell.util.DataAccessException; import org.vcell.util.document.KeyValue; @@ -19,12 +21,13 @@ import java.io.File; import java.io.IOException; import java.sql.*; -import java.time.Instant; @ApplicationScoped public class OptimizationRestService { private static final Logger lg = LogManager.getLogger(OptimizationRestService.class); + private static final OptJobTable optJobTable = OptJobTable.table; + private final AgroalConnectionFactory connectionFactory; private final KeyFactory keyFactory; private final ObjectMapper objectMapper; @@ -62,10 +65,12 @@ public OptimizationJobStatus submitOptimization(OptProblem optProblem, File pare CopasiUtils.writeOptProblem(optProblemFile, optProblem); // Insert the database record - Instant now = Instant.now(); - insertOptJob(con, jobKey, user, OptJobStatus.SUBMITTED, + String sql = "INSERT INTO " + optJobTable.getTableName() + + " " + optJobTable.getSQLColumnList() + + " VALUES " + optJobTable.getSQLValueList(jobKey, user.getID(), OptJobStatus.SUBMITTED, optProblemFile.getAbsolutePath(), optOutputFile.getAbsolutePath(), optReportFile.getAbsolutePath(), - null, null, now); + null, null); + executeUpdate(con, sql); con.commit(); return new OptimizationJobStatus(jobKey, OptJobStatus.SUBMITTED, null, null, null, null); @@ -86,32 +91,32 @@ public OptimizationJobStatus getOptimizationStatus(KeyValue jobKey, User user) if (record == null) { throw new DataAccessException("Optimization job not found: " + jobKey); } - if (!record.ownerKey.equals(user.getID())) { + if (!record.ownerKey().equals(user.getID())) { throw new DataAccessException("Not authorized to access optimization job: " + jobKey); } OptProgressReport progressReport = null; Vcellopt results = null; - switch (record.status) { + switch (record.status()) { case RUNNING: case QUEUED: // Try to read progress from the report file - progressReport = readProgressReport(record.optReportFile); + progressReport = readProgressReport(record.optReportFile()); // Check if the output file has appeared (solver completed) - results = readResults(record.optOutputFile); + results = readResults(record.optOutputFile()); if (results != null) { updateOptJobStatus(jobKey, OptJobStatus.COMPLETE, null); - return new OptimizationJobStatus(jobKey, OptJobStatus.COMPLETE, null, record.htcJobId, progressReport, results); + return new OptimizationJobStatus(jobKey, OptJobStatus.COMPLETE, null, record.htcJobId(), progressReport, results); } break; case COMPLETE: - progressReport = readProgressReport(record.optReportFile); - results = readResults(record.optOutputFile); + progressReport = readProgressReport(record.optReportFile()); + results = readResults(record.optOutputFile()); break; case STOPPED: // After stop, the report file has progress up to the kill point - progressReport = readProgressReport(record.optReportFile); + progressReport = readProgressReport(record.optReportFile()); break; case FAILED: case SUBMITTED: @@ -119,7 +124,7 @@ public OptimizationJobStatus getOptimizationStatus(KeyValue jobKey, User user) break; } - return new OptimizationJobStatus(jobKey, record.status, record.statusMessage, record.htcJobId, progressReport, results); + return new OptimizationJobStatus(jobKey, record.status(), record.statusMessage(), record.htcJobId(), progressReport, results); } /** @@ -130,13 +135,10 @@ public void updateOptJobStatus(KeyValue jobKey, OptJobStatus status, String stat Connection con = null; try { con = connectionFactory.getConnection(this); - PreparedStatement stmt = con.prepareStatement( - "UPDATE vc_optjob SET status = ?, statusMessage = ?, updateDate = ? WHERE id = ?"); - stmt.setString(1, status.name()); - stmt.setString(2, statusMessage); - stmt.setTimestamp(3, Timestamp.from(Instant.now())); - stmt.setLong(4, Long.parseLong(jobKey.toString())); - stmt.executeUpdate(); + String sql = "UPDATE " + optJobTable.getTableName() + + " SET " + optJobTable.getSQLUpdateList(status, statusMessage) + + " WHERE " + optJobTable.id + "=" + jobKey; + executeUpdate(con, sql); con.commit(); } catch (SQLException e) { if (con != null) con.rollback(); @@ -153,13 +155,10 @@ public void updateHtcJobId(KeyValue jobKey, String htcJobId) throws SQLException Connection con = null; try { con = connectionFactory.getConnection(this); - PreparedStatement stmt = con.prepareStatement( - "UPDATE vc_optjob SET htcJobId = ?, status = ?, updateDate = ? WHERE id = ?"); - stmt.setString(1, htcJobId); - stmt.setString(2, OptJobStatus.QUEUED.name()); - stmt.setTimestamp(3, Timestamp.from(Instant.now())); - stmt.setLong(4, Long.parseLong(jobKey.toString())); - stmt.executeUpdate(); + String sql = "UPDATE " + optJobTable.getTableName() + + " SET " + optJobTable.getSQLUpdateListHtcJobId(htcJobId, OptJobStatus.QUEUED) + + " WHERE " + optJobTable.id + "=" + jobKey; + executeUpdate(con, sql); con.commit(); } catch (SQLException e) { if (con != null) con.rollback(); @@ -177,7 +176,7 @@ public String getHtcJobId(KeyValue jobKey) throws SQLException, DataAccessExcept if (record == null) { throw new DataAccessException("Optimization job not found: " + jobKey); } - return record.htcJobId; + return record.htcJobId(); } /** @@ -187,18 +186,24 @@ public OptimizationJobStatus[] listOptimizationJobs(User user) throws SQLExcepti Connection con = null; try { con = connectionFactory.getConnection(this); - PreparedStatement stmt = con.prepareStatement( - "SELECT id, status, htcJobId, statusMessage, insertDate, updateDate " + - "FROM vc_optjob WHERE ownerRef = ? ORDER BY insertDate DESC"); - stmt.setLong(1, Long.parseLong(user.getID().toString())); - ResultSet rs = stmt.executeQuery(); + String sql = "SELECT " + optJobTable.id + "," + + optJobTable.status + "," + + optJobTable.htcJobId + "," + + optJobTable.statusMessage + "," + + optJobTable.insertDate + "," + + optJobTable.updateDate + + " FROM " + optJobTable.getTableName() + + " WHERE " + optJobTable.ownerRef + "=" + user.getID() + + " ORDER BY " + optJobTable.insertDate + " DESC"; + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery(sql); java.util.List jobs = new java.util.ArrayList<>(); while (rs.next()) { jobs.add(new OptimizationJobStatus( - new KeyValue(rs.getBigDecimal("id")), - OptJobStatus.valueOf(rs.getString("status")), - rs.getString("statusMessage"), - rs.getString("htcJobId"), + new KeyValue(rs.getBigDecimal(optJobTable.id.toString())), + OptJobStatus.valueOf(rs.getString(optJobTable.status.toString())), + rs.getString(optJobTable.statusMessage.toString()), + rs.getString(optJobTable.htcJobId.toString()), null, null )); @@ -211,48 +216,16 @@ public OptimizationJobStatus[] listOptimizationJobs(User user) throws SQLExcepti // --- Private helpers --- - private void insertOptJob(Connection con, KeyValue jobKey, User user, OptJobStatus status, - String optProblemFile, String optOutputFile, String optReportFile, - String htcJobId, String statusMessage, Instant now) throws SQLException { - PreparedStatement stmt = con.prepareStatement( - "INSERT INTO vc_optjob (id, ownerRef, status, optProblemFile, optOutputFile, optReportFile, " + - "htcJobId, statusMessage, insertDate, updateDate) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - stmt.setLong(1, Long.parseLong(jobKey.toString())); - stmt.setLong(2, Long.parseLong(user.getID().toString())); - stmt.setString(3, status.name()); - stmt.setString(4, optProblemFile); - stmt.setString(5, optOutputFile); - stmt.setString(6, optReportFile); - stmt.setString(7, htcJobId); - stmt.setString(8, statusMessage); - stmt.setTimestamp(9, Timestamp.from(now)); - stmt.setTimestamp(10, Timestamp.from(now)); - stmt.executeUpdate(); - } - private OptJobRecord getOptJobRecord(KeyValue jobKey) throws SQLException { Connection con = null; try { con = connectionFactory.getConnection(this); - PreparedStatement stmt = con.prepareStatement( - "SELECT j.id, j.ownerRef, j.status, j.optProblemFile, j.optOutputFile, j.optReportFile, " + - "j.htcJobId, j.statusMessage, j.insertDate, j.updateDate " + - "FROM vc_optjob j WHERE j.id = ?"); - stmt.setLong(1, Long.parseLong(jobKey.toString())); - ResultSet rs = stmt.executeQuery(); + String sql = "SELECT * FROM " + optJobTable.getTableName() + + " WHERE " + optJobTable.id + "=" + jobKey; + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery(sql); if (rs.next()) { - return new OptJobRecord( - new KeyValue(rs.getBigDecimal("id")), - new KeyValue(rs.getBigDecimal("ownerRef")), - OptJobStatus.valueOf(rs.getString("status")), - rs.getString("optProblemFile"), - rs.getString("optOutputFile"), - rs.getString("optReportFile"), - rs.getString("htcJobId"), - rs.getString("statusMessage"), - rs.getTimestamp("insertDate").toInstant(), - rs.getTimestamp("updateDate").toInstant() - ); + return optJobTable.getOptJobRecord(rs); } return null; } finally { @@ -260,6 +233,19 @@ private OptJobRecord getOptJobRecord(KeyValue jobKey) throws SQLException { } } + private void executeUpdate(Connection con, String sql) throws SQLException { + if (lg.isDebugEnabled()) lg.debug(sql); + Statement stmt = con.createStatement(); + try { + int rowCount = stmt.executeUpdate(sql); + if (rowCount != 1) { + throw new SQLException("Expected 1 row, got " + rowCount); + } + } finally { + stmt.close(); + } + } + private OptProgressReport readProgressReport(String reportFilePath) { try { File f = new File(reportFilePath); @@ -283,20 +269,4 @@ private Vcellopt readResults(String outputFilePath) { } return null; } - - /** - * Internal record for database row data. - */ - record OptJobRecord( - KeyValue id, - KeyValue ownerKey, - OptJobStatus status, - String optProblemFile, - String optOutputFile, - String optReportFile, - String htcJobId, - String statusMessage, - Instant insertDate, - Instant updateDate - ) {} } diff --git a/vcell-rest/src/main/resources/scripts/init.sql b/vcell-rest/src/main/resources/scripts/init.sql index 87cdcda404..d08f5d3033 100644 --- a/vcell-rest/src/main/resources/scripts/init.sql +++ b/vcell-rest/src/main/resources/scripts/init.sql @@ -76,7 +76,7 @@ CREATE TABLE vc_userlogininfo(id bigint PRIMARY KEY,userRef bigint NOT NULL REFE CREATE TABLE vc_metadata(id bigint PRIMARY KEY,bioModelRef bigint NOT NULL REFERENCES vc_biomodel(id) ON DELETE CASCADE,vcMetaDataLarge text ,vcMetaDataSmall varchar(4000) ); CREATE TABLE vc_simdelfromdisk(deldate varchar(20) ,userid varchar(255) NOT NULL,userkey bigint ,simid bigint ,simpref bigint ,simdate varchar(20) ,simname varchar(255) NOT NULL,status varchar(10) ,numfiles bigint ,totalsize bigint ); CREATE TABLE vc_useridentity(id bigint PRIMARY KEY,userRef bigint NOT NULL REFERENCES vc_userinfo(id),authSubject varchar(128) NOT NULL,authIssuer varchar(128) NOT NULL,insertDate timestamp NOT NULL); -CREATE TABLE vc_optjob(id bigint PRIMARY KEY,ownerRef bigint NOT NULL REFERENCES vc_userinfo(id),status varchar(32) NOT NULL,optProblemFile varchar(512) NOT NULL,optOutputFile varchar(512) NOT NULL,optReportFile varchar(512) NOT NULL,htcJobId varchar(128),statusMessage varchar(4000),insertDate timestamp NOT NULL,updateDate timestamp NOT NULL); +CREATE TABLE vc_optjob(id bigint PRIMARY KEY,ownerRef bigint NOT NULL REFERENCES vc_userinfo(id),status varchar(32) NOT NULL,optProblemFile varchar(512) NOT NULL,optOutputFile varchar(512) NOT NULL,optReportFile varchar(512) NOT NULL,htcJobId varchar(128) ,statusMessage varchar(4000) ,insertDate timestamp NOT NULL,updateDate timestamp NOT NULL); CREATE VIEW public.dual AS SELECT CAST('X' as varchar) AS dummy; @@ -89,6 +89,7 @@ CREATE INDEX geom_imageref ON vc_geometry(imageRef); CREATE INDEX mathdesc_geomref ON vc_math(geometryRef); CREATE INDEX simcstat_simcref ON vc_simcontextstat(simContextRef); + INSERT INTO vc_userinfo VALUES ( 0,'void','1700596370242','void@example.com','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty',CURRENT_TIMESTAMP,'B9BDD75BC5382CA83D5AB82172A98D869555899C' ); INSERT INTO vc_userinfo VALUES ( 2,'Administrator','1700596370260','Administrator@example.com','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty',CURRENT_TIMESTAMP,'CD181552B879A2F29D10434D8ACF692B6C8126F9' ); INSERT INTO vc_userinfo VALUES ( 3,'vcellNagios','1700596370261','vcellNagios@example.com','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty',CURRENT_TIMESTAMP,'A93453F7962799355608EC89D33D3249474E538F' ); diff --git a/vcell-server/src/main/java/cbit/vcell/modeldb/OptJobTable.java b/vcell-server/src/main/java/cbit/vcell/modeldb/OptJobTable.java new file mode 100644 index 0000000000..9ca7911942 --- /dev/null +++ b/vcell-server/src/main/java/cbit/vcell/modeldb/OptJobTable.java @@ -0,0 +1,93 @@ +package cbit.vcell.modeldb; + +import cbit.sql.Field; +import cbit.sql.Field.SQLDataType; +import cbit.sql.Table; +import org.vcell.optimization.OptJobRecord; +import org.vcell.optimization.OptJobStatus; +import org.vcell.util.TokenMangler; +import org.vcell.util.document.KeyValue; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Table definition for vc_optjob — optimization job tracking. + * This is an operational table (not versioned). Access control is enforced + * at the REST layer by checking ownerRef. + */ +public class OptJobTable extends Table { + private static final String TABLE_NAME = "vc_optjob"; + public static final String REF_TYPE = "REFERENCES " + TABLE_NAME + "(" + Table.id_ColumnName + ")"; + + public final Field ownerRef = new Field("ownerRef", SQLDataType.integer, "NOT NULL " + UserTable.REF_TYPE); + public final Field status = new Field("status", SQLDataType.varchar2_32, "NOT NULL"); + public final Field optProblemFile = new Field("optProblemFile", SQLDataType.varchar2_512, "NOT NULL"); + public final Field optOutputFile = new Field("optOutputFile", SQLDataType.varchar2_512, "NOT NULL"); + public final Field optReportFile = new Field("optReportFile", SQLDataType.varchar2_512, "NOT NULL"); + public final Field htcJobId = new Field("htcJobId", SQLDataType.varchar2_128, ""); + public final Field statusMessage = new Field("statusMessage", SQLDataType.varchar2_4000, ""); + public final Field insertDate = new Field("insertDate", SQLDataType.date, "NOT NULL"); + public final Field updateDate = new Field("updateDate", SQLDataType.date, "NOT NULL"); + + private final Field[] fields = { + ownerRef, status, optProblemFile, optOutputFile, optReportFile, + htcJobId, statusMessage, insertDate, updateDate + }; + + public static final OptJobTable table = new OptJobTable(); + + private OptJobTable() { + super(TABLE_NAME); + addFields(fields); + } + + public String getSQLValueList(KeyValue key, KeyValue ownerKey, OptJobStatus jobStatus, + String problemFile, String outputFile, String reportFile, + String htcJob, String message) { + return "(" + + key + "," + + ownerKey + "," + + "'" + jobStatus.name() + "'," + + "'" + TokenMangler.getSQLEscapedString(problemFile) + "'," + + "'" + TokenMangler.getSQLEscapedString(outputFile) + "'," + + "'" + TokenMangler.getSQLEscapedString(reportFile) + "'," + + (htcJob != null ? "'" + TokenMangler.getSQLEscapedString(htcJob) + "'" : "null") + "," + + (message != null ? "'" + TokenMangler.getSQLEscapedString(message, 4000) + "'" : "null") + "," + + "current_timestamp," + + "current_timestamp" + + ")"; + } + + public String getSQLUpdateList(OptJobStatus jobStatus, String message) { + return status + "='" + jobStatus.name() + "'," + + statusMessage + "=" + (message != null ? "'" + TokenMangler.getSQLEscapedString(message, 4000) + "'" : "null") + "," + + updateDate + "=current_timestamp"; + } + + public String getSQLUpdateListHtcJobId(String htcJob, OptJobStatus jobStatus) { + return htcJobId + "=" + (htcJob != null ? "'" + TokenMangler.getSQLEscapedString(htcJob) + "'" : "null") + "," + + status + "='" + jobStatus.name() + "'," + + updateDate + "=current_timestamp"; + } + + public OptJobRecord getOptJobRecord(ResultSet rset) throws SQLException { + KeyValue key = new KeyValue(rset.getBigDecimal(id.toString())); + KeyValue owner = new KeyValue(rset.getBigDecimal(ownerRef.toString())); + OptJobStatus jobStatus = OptJobStatus.valueOf(rset.getString(status.toString())); + String problemFile = rset.getString(optProblemFile.toString()); + String outputFile = rset.getString(optOutputFile.toString()); + String reportFile = rset.getString(optReportFile.toString()); + String htcJob = rset.getString(htcJobId.toString()); + String rawMessage = rset.getString(statusMessage.toString()); + String message = rawMessage != null ? TokenMangler.getSQLRestoredString(rawMessage) : null; + java.sql.Timestamp insertTs = rset.getTimestamp(insertDate.toString()); + java.sql.Timestamp updateTs = rset.getTimestamp(updateDate.toString()); + + return new OptJobRecord( + key, owner, jobStatus, problemFile, outputFile, reportFile, + htcJob, message, + insertTs.toInstant(), updateTs.toInstant() + ); + } +} diff --git a/vcell-server/src/main/java/cbit/vcell/modeldb/SQLCreateAllTables.java b/vcell-server/src/main/java/cbit/vcell/modeldb/SQLCreateAllTables.java index 58eb9648ee..5892fab91f 100644 --- a/vcell-server/src/main/java/cbit/vcell/modeldb/SQLCreateAllTables.java +++ b/vcell-server/src/main/java/cbit/vcell/modeldb/SQLCreateAllTables.java @@ -376,6 +376,7 @@ public static Table[] getVCellTables() { cbit.vcell.modeldb.VCMetaDataTable.table, // new cbit.vcell.modeldb.SimDelFromDiskTable.table, // new UserIdentityTable.table, + OptJobTable.table, }; return tables; } From 093ce26a9f3cc3c7572c149967e621d3056c6c46 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Thu, 9 Apr 2026 00:56:19 -0400 Subject: [PATCH 19/40] Configure AMQP connection to Artemis broker Map existing K8s configmap/secret env vars (jmshost_artemis_internal, jmsport_artemis_internal, AMQP_USER, AMQP_PASSWORD) to SmallRye AMQP connection properties so the REST pod can connect to Artemis for optimization job messaging. Co-Authored-By: Claude Opus 4.6 (1M context) --- vcell-rest/src/main/resources/application.properties | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vcell-rest/src/main/resources/application.properties b/vcell-rest/src/main/resources/application.properties index 5231f2745a..48b2823895 100644 --- a/vcell-rest/src/main/resources/application.properties +++ b/vcell-rest/src/main/resources/application.properties @@ -186,6 +186,10 @@ quarkus.swagger-ui.always-include=true %test.mp.messaging.incoming.subscriber-opt-status.address=opt-status quarkus.amqp.devservices.enabled=false +amqp-host=${jmshost_artemis_internal:localhost} +amqp-port=${jmsport_artemis_internal:5672} +amqp-username=${AMQP_USER:guest} +amqp-password=${AMQP_PASSWORD:guest} vcell.exporter=false From 6275a147bb9be2a01585ab96c11be19233203221 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Thu, 9 Apr 2026 07:14:55 -0400 Subject: [PATCH 20/40] Fix export tests: use ManagedExecutor for CDI context propagation Replace Executors.newFixedThreadPool with Quarkus ManagedExecutor in ExportRequestListenerMQ so async export jobs run on threads with CDI context, fixing PropertyLoader access failures. Apply same fix in ExportServerTest. Scope AMQP connection properties to %prod profile so DevServices handles test AMQP configuration. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../org/vcell/restq/activemq/ExportRequestListenerMQ.java | 8 +++++--- vcell-rest/src/main/resources/application.properties | 8 ++++---- .../java/org/vcell/restq/exports/ExportServerTest.java | 6 +++++- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/vcell-rest/src/main/java/org/vcell/restq/activemq/ExportRequestListenerMQ.java b/vcell-rest/src/main/java/org/vcell/restq/activemq/ExportRequestListenerMQ.java index f8f2456367..434c9e6da4 100644 --- a/vcell-rest/src/main/java/org/vcell/restq/activemq/ExportRequestListenerMQ.java +++ b/vcell-rest/src/main/java/org/vcell/restq/activemq/ExportRequestListenerMQ.java @@ -31,7 +31,8 @@ import java.io.FileNotFoundException; import java.sql.SQLException; import java.util.Map; -import java.util.concurrent.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; @@ -59,10 +60,11 @@ public CompletableFuture startJob(Message exportJob) { @IfBuildProperty(name = "vcell.exporter", stringValue = "true") public class ExportRequestListenerMQ implements ExportMQInterface { private static final Logger logger = LogManager.getLogger(ExportRequestListenerMQ.class); - private final ExecutorService threadPoolExecutor = Executors.newFixedThreadPool(10); private DataServerImpl dataServer; private TimeUnit waitUnit = TimeUnit.MINUTES; + @Inject + org.eclipse.microprofile.context.ManagedExecutor managedExecutor; @Inject ServerExportEventController exportStatusCreator; @Inject @@ -118,7 +120,7 @@ public CompletableFuture startJob(Message message, boolean handleF } catch (SQLException | DataAccessException e) { throw new RuntimeException(e); } - }, threadPoolExecutor) + }, managedExecutor) .orTimeout(15, waitUnit); if (handleFailure){ diff --git a/vcell-rest/src/main/resources/application.properties b/vcell-rest/src/main/resources/application.properties index 48b2823895..d229790929 100644 --- a/vcell-rest/src/main/resources/application.properties +++ b/vcell-rest/src/main/resources/application.properties @@ -186,10 +186,10 @@ quarkus.swagger-ui.always-include=true %test.mp.messaging.incoming.subscriber-opt-status.address=opt-status quarkus.amqp.devservices.enabled=false -amqp-host=${jmshost_artemis_internal:localhost} -amqp-port=${jmsport_artemis_internal:5672} -amqp-username=${AMQP_USER:guest} -amqp-password=${AMQP_PASSWORD:guest} +%prod.amqp-host=${jmshost_artemis_internal:localhost} +%prod.amqp-port=${jmsport_artemis_internal:5672} +%prod.amqp-username=${AMQP_USER:guest} +%prod.amqp-password=${AMQP_PASSWORD:guest} vcell.exporter=false diff --git a/vcell-rest/src/test/java/org/vcell/restq/exports/ExportServerTest.java b/vcell-rest/src/test/java/org/vcell/restq/exports/ExportServerTest.java index bb7f3d9378..d21b2daf5f 100644 --- a/vcell-rest/src/test/java/org/vcell/restq/exports/ExportServerTest.java +++ b/vcell-rest/src/test/java/org/vcell/restq/exports/ExportServerTest.java @@ -40,6 +40,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.eclipse.microprofile.context.ManagedExecutor; + @QuarkusTest public class ExportServerTest { @Inject @@ -53,6 +55,8 @@ public class ExportServerTest { ExportService exportService; @Inject ObjectMapper mapper; + @Inject + ManagedExecutor managedExecutor; private DataServerImpl dataServer; private final String simulationID = "597714292"; @@ -98,7 +102,7 @@ public void testExportStatus() throws Exception { // If an exception is thrown during the export process, the blocking iterable will hang because no finish statement has been sent throw new RuntimeException(e); } - }); + }, managedExecutor); int i = 0; for (ExportEvent exportEvent : blockingIterable) { switch (i){ From 370bf7bc99bbabd2dd3b82f2cdd2fa255f6edeed Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Thu, 9 Apr 2026 07:42:58 -0400 Subject: [PATCH 21/40] Parallelize Docker image builds with matrix strategy Split the single sequential build job into parallel matrix jobs: - maven-build: compiles Java once, uploads JARs as artifacts - docker-build: 17 parallel jobs, one per image (api, rest, exporter, 5 webapp variants, db, sched, submit, data, mongo, batch, opt, clientgen, admin) - tag-and-push: tags all images with friendly version and latest Installer secrets for clientgen are fetched directly via SSH in that matrix job rather than uploaded as artifacts (security: artifacts are downloadable on public repos). Previously all 13+ Docker builds ran sequentially in one job taking 6+ hours. With matrix parallelization, total wall time should be limited by the slowest single image build. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/CI-full.yml | 295 ++++++++++++++++++++++++++-------- 1 file changed, 230 insertions(+), 65 deletions(-) diff --git a/.github/workflows/CI-full.yml b/.github/workflows/CI-full.yml index 8402ac1df1..8d121fdd6d 100644 --- a/.github/workflows/CI-full.yml +++ b/.github/workflows/CI-full.yml @@ -6,19 +6,36 @@ on: tag: description: "Repository tag (e.g., 7.7.0.37)" required: true -# client_only: -# description: "Build only client images (true/false)" -# required: false -# default: "false" release: types: [published] - + env: python-version: "3.10" - + jobs: - build: + setup: + runs-on: ubuntu-22.04 + outputs: + vcell_tag: ${{ steps.set-vars.outputs.vcell_tag }} + vcell_repo_namespace: ${{ steps.set-vars.outputs.vcell_repo_namespace }} + friendly_tag: ${{ steps.set-vars.outputs.friendly_tag }} + steps: + - uses: actions/checkout@v4 + + - name: set global environment variables + id: set-vars + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "friendly_tag=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT + else + echo "friendly_tag=${GITHUB_REF:10}" >> $GITHUB_OUTPUT + fi + echo "vcell_tag=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + echo "vcell_repo_namespace=ghcr.io/virtualcell" >> $GITHUB_OUTPUT + + maven-build: runs-on: ubuntu-22.04 + needs: setup steps: - name: Free up VM's disk space run: | @@ -30,37 +47,12 @@ jobs: - uses: actions/checkout@v4 - - name: set global environment variables - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "FRIENDLY_TAG=${{ github.event.inputs.tag }}" >> $GITHUB_ENV - else - echo "FRIENDLY_TAG=${GITHUB_REF:10}" >> $GITHUB_ENV - fi - echo "VCELL_TAG=`git rev-parse --short HEAD`" >> $GITHUB_ENV - echo "VCELL_REPO_NAMESPACE=ghcr.io/virtualcell" >> $GITHUB_ENV - echo "VCELL_DEPLOY_REMOTE_DIR=/share/apps/vcell3/deployed_github" >> $GITHUB_ENV - echo "VCELL_MANAGER_NODE=vcellapi.cam.uchc.edu" >> $GITHUB_ENV - - name: setup ssh-agent - uses: webfactory/ssh-agent@v0.8.0 - with: - ssh-private-key: ${{ secrets.VC_KEY }} - - name: get installer secrets - run: | - ssh-keyscan ${VCELL_MANAGER_NODE} >> ~/.ssh/known_hosts - sudo mkdir /usr/local/deploy - sudo chmod 777 /usr/local/deploy - cd /usr/local/deploy - scp ${{ secrets.CD_FULL_USER }}@${VCELL_MANAGER_NODE}:${VCELL_DEPLOY_REMOTE_DIR}/deploy_dir_2025_03_18.tar . - cd .. - sudo tar -xvf deploy/deploy_dir_2025_03_18.tar - sudo chmod 777 -R deploy - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ env.python-version }} cache: "pip" - + - name: Install Dependencies run: pip install -r requirements.txt @@ -81,42 +73,215 @@ jobs: java-version: '17' cache: 'maven' - - name: build and publish all images - shell: bash + - name: Maven build + run: mvn --batch-mode clean install dependency:copy-dependencies -DskipTests=true + + - name: Upload Maven artifacts + uses: actions/upload-artifact@v4 + with: + name: maven-build-output + retention-days: 1 + path: | + vcell-server/target/vcell-server-0.0.1-SNAPSHOT.jar + vcell-server/target/maven-jars/ + vcell-api/target/vcell-api-0.0.1-SNAPSHOT.jar + vcell-api/target/maven-jars/ + vcell-client/target/vcell-client-0.0.1-SNAPSHOT.jar + vcell-client/target/maven-jars/ + vcell-admin/target/vcell-admin-0.0.1-SNAPSHOT.jar + vcell-admin/target/maven-jars/ + + + docker-build: + runs-on: ubuntu-22.04 + needs: [setup, maven-build] + strategy: + fail-fast: false + matrix: + image: + - name: api + dockerfile: docker/build/Dockerfile-api-dev + context: . + needs_maven: true + needs_secrets: false + - name: rest + dockerfile: vcell-rest/src/main/docker/Dockerfile.jvm + context: vcell-rest + needs_maven: false + needs_secrets: false + pre_build: "mvn clean install -DskipTests -Dvcell.exporter=false -f vcell-rest/pom.xml" + - name: exporter + dockerfile: vcell-rest/src/main/docker/Dockerfile.jvm + context: vcell-rest + needs_maven: false + needs_secrets: false + pre_build: "mvn clean install -DskipTests -Dvcell.exporter=true -f vcell-rest/pom.xml" + - name: webapp-dev + dockerfile: webapp-ng/Dockerfile-webapp + context: webapp-ng + needs_maven: false + needs_secrets: false + build_args: "BUILD_COMMAND=build_dev" + - name: webapp-stage + dockerfile: webapp-ng/Dockerfile-webapp + context: webapp-ng + needs_maven: false + needs_secrets: false + build_args: "BUILD_COMMAND=build_stage" + - name: webapp-prod + dockerfile: webapp-ng/Dockerfile-webapp + context: webapp-ng + needs_maven: false + needs_secrets: false + build_args: "BUILD_COMMAND=build_prod" + - name: webapp-island + dockerfile: webapp-ng/Dockerfile-webapp + context: webapp-ng + needs_maven: false + needs_secrets: false + build_args: "BUILD_COMMAND=build_island" + - name: webapp-remote + dockerfile: webapp-ng/Dockerfile-webapp + context: webapp-ng + needs_maven: false + needs_secrets: false + build_args: "BUILD_COMMAND=build_remote" + - name: db + dockerfile: docker/build/Dockerfile-db-dev + context: . + needs_maven: true + needs_secrets: false + - name: sched + dockerfile: docker/build/Dockerfile-sched-dev + context: . + needs_maven: true + needs_secrets: false + - name: submit + dockerfile: docker/build/Dockerfile-submit-dev + context: . + needs_maven: true + needs_secrets: false + - name: data + dockerfile: docker/build/Dockerfile-data-dev + context: . + needs_maven: true + needs_secrets: false + - name: mongo + dockerfile: docker/build/mongo/Dockerfile + context: docker/build/mongo + needs_maven: false + needs_secrets: false + - name: batch + dockerfile: docker/build/Dockerfile-batch-dev + context: . + needs_maven: true + needs_secrets: false + - name: opt + dockerfile: pythonCopasiOpt/Dockerfile + context: pythonCopasiOpt + needs_maven: false + needs_secrets: false + - name: clientgen + dockerfile: docker/build/Dockerfile-clientgen-dev + context: . + needs_maven: true + needs_secrets: false + needs_installer_secrets: true + - name: admin + dockerfile: docker/build/Dockerfile-admin-dev + context: . + needs_maven: true + needs_secrets: false + steps: + - name: Free up VM's disk space + run: | + sudo rm -rf /usr/local/lib/android + sudo rm -rf /usr/local/.ghcup + sudo rm -rf /usr/share/dotnet + sudo rm -rf /usr/share/swift + sudo apt-get clean + + - uses: actions/checkout@v4 + + - name: setup java 17 with maven cache + if: ${{ matrix.image.pre_build }} + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + cache: 'maven' + + - name: Download Maven artifacts + if: ${{ matrix.image.needs_maven }} + uses: actions/download-artifact@v4 + with: + name: maven-build-output + path: . + + - name: setup ssh-agent for installer secrets + if: ${{ matrix.image.needs_installer_secrets }} + uses: webfactory/ssh-agent@v0.8.0 + with: + ssh-private-key: ${{ secrets.VC_KEY }} + + - name: get installer secrets + if: ${{ matrix.image.needs_installer_secrets }} + env: + VCELL_MANAGER_NODE: vcellapi.cam.uchc.edu + VCELL_DEPLOY_REMOTE_DIR: /share/apps/vcell3/deployed_github + run: | + ssh-keyscan ${VCELL_MANAGER_NODE} >> ~/.ssh/known_hosts + sudo mkdir /usr/local/deploy + sudo chmod 777 /usr/local/deploy + cd /usr/local/deploy + scp ${{ secrets.CD_FULL_USER }}@${VCELL_MANAGER_NODE}:${VCELL_DEPLOY_REMOTE_DIR}/deploy_dir_2025_03_18.tar . + cd .. + sudo tar -xvf deploy/deploy_dir_2025_03_18.tar + sudo chmod 777 -R deploy + + - name: Pre-build step + if: ${{ matrix.image.pre_build }} + run: ${{ matrix.image.pre_build }} + + - name: Log in to GHCR + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + - name: Build and push Docker image run: | - cd docker/build - echo "${{ secrets.GITHUB_TOKEN }}" | sudo docker login ghcr.io -u ${{ github.actor }} --password-stdin - echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - export BUILD_IMAGES="all" - ./build.sh ${BUILD_IMAGES} ${{ env.VCELL_REPO_NAMESPACE }} ${{ env.VCELL_TAG }} - - - name: tag as latest and push to registry - shell: bash + IMAGE_NAME="vcell-${{ matrix.image.name }}" + FULL_TAG="${{ needs.setup.outputs.vcell_repo_namespace }}/${IMAGE_NAME}:${{ needs.setup.outputs.vcell_tag }}" + BUILD_ARGS="" + if [ -n "${{ matrix.image.build_args || '' }}" ]; then + BUILD_ARGS="--build-arg ${{ matrix.image.build_args }}" + fi + echo "Building ${FULL_TAG}" + docker buildx build --platform=linux/amd64 \ + ${BUILD_ARGS} \ + -f ${{ matrix.image.dockerfile }} \ + --tag ${FULL_TAG} \ + ${{ matrix.image.context }} + docker push ${FULL_TAG} + + tag-and-push: + runs-on: ubuntu-22.04 + needs: [setup, docker-build] + steps: + - name: Log in to GHCR + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + - name: Tag and push all images run: | - export CONTAINER_SET="vcell-exporter vcell-api vcell-rest vcell-webapp-prod vcell-webapp-dev vcell-webapp-stage vcell-webapp-island vcell-batch vcell-opt vcell-clientgen vcell-data vcell-db vcell-mongo vcell-sched vcell-submit vcell-admin" - for CONTAINER in ${CONTAINER_SET};\ - do docker tag ${VCELL_REPO_NAMESPACE}/$CONTAINER:${VCELL_TAG} ${VCELL_REPO_NAMESPACE}/$CONTAINER:latest;\ - docker tag ${VCELL_REPO_NAMESPACE}/$CONTAINER:${VCELL_TAG} ${VCELL_REPO_NAMESPACE}/$CONTAINER:${FRIENDLY_TAG};\ - docker push --all-tags ${VCELL_REPO_NAMESPACE}/$CONTAINER;\ + REPO="${{ needs.setup.outputs.vcell_repo_namespace }}" + TAG="${{ needs.setup.outputs.vcell_tag }}" + FRIENDLY="${{ needs.setup.outputs.friendly_tag }}" + CONTAINERS="vcell-exporter vcell-api vcell-rest vcell-webapp-prod vcell-webapp-dev vcell-webapp-stage vcell-webapp-island vcell-webapp-remote vcell-batch vcell-opt vcell-clientgen vcell-data vcell-db vcell-mongo vcell-sched vcell-submit vcell-admin" + for CONTAINER in ${CONTAINERS}; do + docker pull ${REPO}/${CONTAINER}:${TAG} + docker tag ${REPO}/${CONTAINER}:${TAG} ${REPO}/${CONTAINER}:latest + docker tag ${REPO}/${CONTAINER}:${TAG} ${REPO}/${CONTAINER}:${FRIENDLY} + docker push --all-tags ${REPO}/${CONTAINER} done - name: Setup tmate session uses: mxschmitt/action-tmate@v3 if: ${{ failure() }} - - -# if [ "${{ github.event.inputs.client_only }}" = "true" ]; then -# echo "Building only client images" -# export BUILD_IMAGES="clientgen" -# else -# echo "Building all images" -# -# fi - -# if [ "${{ github.event.inputs.client_only }}" = "true" ]; then -# echo "Building only client images" -# export CONTAINER_SET="vcell-clientgen" -# else -# echo "Building all images" -# export CONTAINER_SET="vcell-exporter vcell-api vcell-rest vcell-webapp-prod vcell-webapp-dev vcell-webapp-stage vcell-webapp-island vcell-batch vcell-opt vcell-clientgen vcell-data vcell-db vcell-mongo vcell-sched vcell-submit vcell-admin" -# fi From a65e4d7e2d83f00efd5410a92a953ff553f38534 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Thu, 9 Apr 2026 11:04:10 -0400 Subject: [PATCH 22/40] Fix CI: upload all Maven target dirs, full rebuild for rest/exporter - Upload all **/target/ directories and localsolvers/ as artifacts so all Docker matrix jobs get the complete Maven build output including transitive dependencies - rest/exporter do full mvn install dependency:copy-dependencies with their respective -Dvcell.exporter flag - localsolvers/ contains solver binaries downloaded by Maven profiles Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/CI-full.yml | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/.github/workflows/CI-full.yml b/.github/workflows/CI-full.yml index 8d121fdd6d..310f1a3573 100644 --- a/.github/workflows/CI-full.yml +++ b/.github/workflows/CI-full.yml @@ -82,14 +82,8 @@ jobs: name: maven-build-output retention-days: 1 path: | - vcell-server/target/vcell-server-0.0.1-SNAPSHOT.jar - vcell-server/target/maven-jars/ - vcell-api/target/vcell-api-0.0.1-SNAPSHOT.jar - vcell-api/target/maven-jars/ - vcell-client/target/vcell-client-0.0.1-SNAPSHOT.jar - vcell-client/target/maven-jars/ - vcell-admin/target/vcell-admin-0.0.1-SNAPSHOT.jar - vcell-admin/target/maven-jars/ + **/target/ + localsolvers/ docker-build: @@ -107,15 +101,15 @@ jobs: - name: rest dockerfile: vcell-rest/src/main/docker/Dockerfile.jvm context: vcell-rest - needs_maven: false + needs_maven: true needs_secrets: false - pre_build: "mvn clean install -DskipTests -Dvcell.exporter=false -f vcell-rest/pom.xml" + pre_build: "mvn --batch-mode clean install dependency:copy-dependencies -DskipTests -Dvcell.exporter=false" - name: exporter dockerfile: vcell-rest/src/main/docker/Dockerfile.jvm context: vcell-rest - needs_maven: false + needs_maven: true needs_secrets: false - pre_build: "mvn clean install -DskipTests -Dvcell.exporter=true -f vcell-rest/pom.xml" + pre_build: "mvn --batch-mode clean install dependency:copy-dependencies -DskipTests -Dvcell.exporter=true" - name: webapp-dev dockerfile: webapp-ng/Dockerfile-webapp context: webapp-ng From 35de69d32369d07089f041eca6b16ec4a1544e38 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Thu, 9 Apr 2026 17:25:57 -0400 Subject: [PATCH 23/40] Fix AMQP address mapping and JDBC resource leaks in optimization service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add production AMQP channel config for opt-request/opt-status queues — without these, SmallRye was sending to the channel name instead of the queue address, so messages never reached vcell-submit. Fix Statement and ResultSet leaks in getOptJobRecord() and listOptimizationJobs() by wrapping in try-with-resources. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../services/OptimizationRestService.java | 38 ++++++++++--------- .../src/main/resources/application.properties | 6 +++ 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java b/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java index 725d7ba227..9d5f3394bf 100644 --- a/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java +++ b/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java @@ -195,20 +195,21 @@ public OptimizationJobStatus[] listOptimizationJobs(User user) throws SQLExcepti " FROM " + optJobTable.getTableName() + " WHERE " + optJobTable.ownerRef + "=" + user.getID() + " ORDER BY " + optJobTable.insertDate + " DESC"; - Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery(sql); - java.util.List jobs = new java.util.ArrayList<>(); - while (rs.next()) { - jobs.add(new OptimizationJobStatus( - new KeyValue(rs.getBigDecimal(optJobTable.id.toString())), - OptJobStatus.valueOf(rs.getString(optJobTable.status.toString())), - rs.getString(optJobTable.statusMessage.toString()), - rs.getString(optJobTable.htcJobId.toString()), - null, - null - )); + try (Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery(sql)) { + java.util.List jobs = new java.util.ArrayList<>(); + while (rs.next()) { + jobs.add(new OptimizationJobStatus( + new KeyValue(rs.getBigDecimal(optJobTable.id.toString())), + OptJobStatus.valueOf(rs.getString(optJobTable.status.toString())), + rs.getString(optJobTable.statusMessage.toString()), + rs.getString(optJobTable.htcJobId.toString()), + null, + null + )); + } + return jobs.toArray(new OptimizationJobStatus[0]); } - return jobs.toArray(new OptimizationJobStatus[0]); } finally { if (con != null) connectionFactory.release(con, this); } @@ -222,12 +223,13 @@ private OptJobRecord getOptJobRecord(KeyValue jobKey) throws SQLException { con = connectionFactory.getConnection(this); String sql = "SELECT * FROM " + optJobTable.getTableName() + " WHERE " + optJobTable.id + "=" + jobKey; - Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery(sql); - if (rs.next()) { - return optJobTable.getOptJobRecord(rs); + try (Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery(sql)) { + if (rs.next()) { + return optJobTable.getOptJobRecord(rs); + } + return null; } - return null; } finally { if (con != null) connectionFactory.release(con, this); } diff --git a/vcell-rest/src/main/resources/application.properties b/vcell-rest/src/main/resources/application.properties index d229790929..cbab3ab8b7 100644 --- a/vcell-rest/src/main/resources/application.properties +++ b/vcell-rest/src/main/resources/application.properties @@ -191,6 +191,12 @@ quarkus.amqp.devservices.enabled=false %prod.amqp-username=${AMQP_USER:guest} %prod.amqp-password=${AMQP_PASSWORD:guest} +mp.messaging.outgoing.publisher-opt-request.connector=smallrye-amqp +mp.messaging.outgoing.publisher-opt-request.address=opt-request + +mp.messaging.incoming.subscriber-opt-status.connector=smallrye-amqp +mp.messaging.incoming.subscriber-opt-status.address=opt-status + vcell.exporter=false From cf5874171d318b490eef9499b695b923c9534e8f Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Thu, 9 Apr 2026 21:43:02 -0400 Subject: [PATCH 24/40] Add Artemis broker config to vcell-submit Dockerfile Pass vcell.jms.artemis.host.internal and vcell.jms.artemis.port.internal as Java system properties so the optimization queue listener can connect to the Artemis broker. Co-Authored-By: Claude Opus 4.6 (1M context) --- docker/build/Dockerfile-submit-dev | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker/build/Dockerfile-submit-dev b/docker/build/Dockerfile-submit-dev index 31e733efb0..c7877b5132 100644 --- a/docker/build/Dockerfile-submit-dev +++ b/docker/build/Dockerfile-submit-dev @@ -90,6 +90,8 @@ ENV softwareVersion=SOFTWARE-VERSION-NOT-SET \ slurm_langevin_timeoutPerTaskSeconds="slurm_langevin_timeoutPerTaskSeconds-not-set" \ slurm_langevin_batchMemoryLimitPerTaskMB="slurm_langevin_batchMemoryLimitPerTaskMB-not-set" \ slurm_langevin_memoryBlockSizeMB="slurm_langevin_memoryBlockSizeMB-not-set" \ + jmshost_artemis_internal=artemismq \ + jmsport_artemis_internal=61616 \ jmsblob_minsize=100000 \ vcell_ssh_cmd_cmdtimeout="cmdSrvcSshCmdTimeoutMS-not-set" \ vcell_ssh_cmd_restoretimeout="cmdSrvcSshCmdRestoreTimeoutFactor-not-set" \ @@ -167,6 +169,8 @@ ENTRYPOINT java \ -Dvcell.jms.sim.host.external="${jmshost_sim_external}" \ -Dvcell.jms.sim.port.external="${jmsport_sim_external}" \ -Dvcell.jms.sim.restport.external="${jmsrestport_sim_external}" \ + -Dvcell.jms.artemis.host.internal="${jmshost_artemis_internal}" \ + -Dvcell.jms.artemis.port.internal="${jmsport_artemis_internal}" \ -Dvcell.jms.blobMessageUseMongo=true \ -Dvcell.jms.blobMessageMinSize="${jmsblob_minsize}" \ -Dvcell.jms.user="${jmsuser}" \ From 0003cfbe702447b1a0f92992120140b47ee72958 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Thu, 9 Apr 2026 23:21:44 -0400 Subject: [PATCH 25/40] Add ANYCAST capabilities to AMQP channels for Artemis cross-protocol routing Add capabilities=queue to both production and test AMQP channel configs so SmallRye attaches as ANYCAST consumer/producer. Without this, Artemis creates MULTICAST subscriptions that miss messages from OpenWire JMS producers on the same queue. Co-Authored-By: Claude Opus 4.6 (1M context) --- vcell-rest/src/main/resources/application.properties | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vcell-rest/src/main/resources/application.properties b/vcell-rest/src/main/resources/application.properties index cbab3ab8b7..310703bd56 100644 --- a/vcell-rest/src/main/resources/application.properties +++ b/vcell-rest/src/main/resources/application.properties @@ -181,9 +181,11 @@ quarkus.swagger-ui.always-include=true %test.mp.messaging.outgoing.publisher-opt-request.connector=smallrye-amqp %test.mp.messaging.outgoing.publisher-opt-request.address=opt-request +%test.mp.messaging.outgoing.publisher-opt-request.capabilities=queue %test.mp.messaging.incoming.subscriber-opt-status.connector=smallrye-amqp %test.mp.messaging.incoming.subscriber-opt-status.address=opt-status +%test.mp.messaging.incoming.subscriber-opt-status.capabilities=queue quarkus.amqp.devservices.enabled=false %prod.amqp-host=${jmshost_artemis_internal:localhost} @@ -193,9 +195,11 @@ quarkus.amqp.devservices.enabled=false mp.messaging.outgoing.publisher-opt-request.connector=smallrye-amqp mp.messaging.outgoing.publisher-opt-request.address=opt-request +mp.messaging.outgoing.publisher-opt-request.capabilities=queue mp.messaging.incoming.subscriber-opt-status.connector=smallrye-amqp mp.messaging.incoming.subscriber-opt-status.address=opt-status +mp.messaging.incoming.subscriber-opt-status.capabilities=queue vcell.exporter=false From 0e4f1f3d1c0201bf4ed0c8ea1f5d9d9e20b43780 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Thu, 9 Apr 2026 23:21:59 -0400 Subject: [PATCH 26/40] Add cross-protocol integration test for optimization AMQP messaging Test the full round-trip through Artemis: vcell-rest publishes via AMQP 1.0, an OpenWire JMS stub (mimicking vcell-submit) consumes and sends status back, vcell-rest consumes the response. This catches address mapping and ANYCAST/MULTICAST routing bugs that the existing E2E test misses by bypassing messaging. - ArtemisTestResource: testcontainer with both AMQP and OpenWire ports - OpenWireOptSubmitStub: mirrors OptimizationBatchServer.handleSubmitRequest() - OptimizationCrossProtocolTest: submit via REST, poll until COMPLETE Co-Authored-By: Claude Opus 4.6 (1M context) --- vcell-rest/pom.xml | 12 ++ .../restq/OptimizationCrossProtocolTest.java | 177 +++++++++++++++ .../testresources/ArtemisTestResource.java | 48 +++++ .../testresources/OpenWireOptSubmitStub.java | 201 ++++++++++++++++++ 4 files changed, 438 insertions(+) create mode 100644 vcell-rest/src/test/java/org/vcell/restq/OptimizationCrossProtocolTest.java create mode 100644 vcell-rest/src/test/java/org/vcell/restq/testresources/ArtemisTestResource.java create mode 100644 vcell-rest/src/test/java/org/vcell/restq/testresources/OpenWireOptSubmitStub.java diff --git a/vcell-rest/pom.xml b/vcell-rest/pom.xml index 56f0fd3c5e..dbfa8d9bbe 100644 --- a/vcell-rest/pom.xml +++ b/vcell-rest/pom.xml @@ -253,6 +253,18 @@ 2.7.0 test + + org.apache.activemq + activemq-client + ${activemq-client.version} + test + + + javax.jms + javax.jms-api + 2.0.1 + test + diff --git a/vcell-rest/src/test/java/org/vcell/restq/OptimizationCrossProtocolTest.java b/vcell-rest/src/test/java/org/vcell/restq/OptimizationCrossProtocolTest.java new file mode 100644 index 0000000000..a35017014b --- /dev/null +++ b/vcell-rest/src/test/java/org/vcell/restq/OptimizationCrossProtocolTest.java @@ -0,0 +1,177 @@ +package org.vcell.restq; + +import cbit.vcell.resource.PropertyLoader; +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.keycloak.client.KeycloakTestClient; +import jakarta.inject.Inject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.junit.jupiter.api.*; +import org.vcell.restclient.ApiClient; +import org.vcell.restclient.api.OptimizationResourceApi; +import org.vcell.restclient.model.OptJobStatus; +import org.vcell.restclient.model.OptimizationJobStatus; +import org.vcell.restq.config.CDIVCellConfigProvider; +import org.vcell.restq.db.AgroalConnectionFactory; +import org.vcell.restq.testresources.ArtemisTestResource; +import org.vcell.restq.testresources.OpenWireOptSubmitStub; +import org.vcell.util.DataAccessException; + +import java.io.File; +import java.sql.SQLException; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Cross-protocol integration test for the optimization messaging pipeline. + * + * Tests the full round-trip: + * REST submit → AMQP 1.0 publish (vcell-rest) → Artemis broker → OpenWire JMS consume (stub) + * → OpenWire JMS publish (stub) → Artemis broker → AMQP 1.0 consume (vcell-rest) → DB update + * → REST poll returns results + * + * The OpenWire stub mimics vcell-submit's OptimizationBatchServer using the same ActiveMQ + * OpenWire protocol. This catches cross-protocol issues like ANYCAST/MULTICAST routing mismatches + * and AMQP address mapping errors. + */ +@QuarkusTest +@Tag("Quarkus") +@QuarkusTestResource(ArtemisTestResource.class) +public class OptimizationCrossProtocolTest { + private static final Logger lg = LogManager.getLogger(OptimizationCrossProtocolTest.class); + + @Inject + AgroalConnectionFactory agroalConnectionFactory; + + @ConfigProperty(name = "quarkus.http.test-port") + Integer testPort; + + @ConfigProperty(name = "vcell.optimization.parest-data-dir") + String parestDataDir; + + @ConfigProperty(name = "artemis.openwire.host") + String openwireHost; + + @ConfigProperty(name = "artemis.openwire.port") + int openwirePort; + + KeycloakTestClient keycloakClient = new KeycloakTestClient(); + OpenWireOptSubmitStub stub; + + @BeforeAll + public static void setupConfig() { + PropertyLoader.setConfigProvider(new CDIVCellConfigProvider()); + } + + @BeforeEach + public void startStub() throws Exception { + stub = new OpenWireOptSubmitStub(openwireHost, openwirePort); + stub.start(); + } + + @AfterEach + public void cleanup() throws Exception { + if (stub != null) { + stub.stop(); + } + TestEndpointUtils.removeAllMappings(agroalConnectionFactory); + } + + /** + * Tests the full cross-protocol round-trip: submit via REST, messages flow through Artemis + * between AMQP 1.0 (vcell-rest) and OpenWire JMS (stub), results returned via REST polling. + */ + @Test + public void testCrossProtocol_submitPollComplete() throws Exception { + // Set up authenticated API client (same as desktop client) + ApiClient apiClient = TestEndpointUtils.createAuthenticatedAPIClient( + keycloakClient, testPort, TestEndpointUtils.TestOIDCUsers.alice); + TestEndpointUtils.mapApiClientToAdmin(apiClient); + OptimizationResourceApi optApi = new OptimizationResourceApi(apiClient); + + // Submit optimization via REST (triggers AMQP 1.0 publish to opt-request) + org.vcell.restclient.model.OptProblem optProblem = createClientOptProblem(); + OptimizationJobStatus submitResult = optApi.submitOptimization(optProblem); + assertNotNull(submitResult.getId()); + assertEquals(OptJobStatus.SUBMITTED, submitResult.getStatus()); + lg.info("Submitted optimization job: {}", submitResult.getId()); + + // Poll for completion — the OpenWire stub processes the request through Artemis + // and sends status back, which vcell-rest consumes via AMQP 1.0 + long jobId = Long.parseLong(submitResult.getId()); + OptimizationJobStatus finalStatus = null; + long startTime = System.currentTimeMillis(); + long timeoutMs = 30_000; + + while ((System.currentTimeMillis() - startTime) < timeoutMs) { + OptimizationJobStatus status = optApi.getOptimizationStatus(jobId); + lg.info("Poll: job={}, status={}", jobId, status.getStatus()); + + if (status.getStatus() == OptJobStatus.COMPLETE) { + finalStatus = status; + break; + } + if (status.getStatus() == OptJobStatus.FAILED) { + fail("Optimization job failed: " + status.getStatusMessage()); + } + + Thread.sleep(500); + } + + // Verify the full round-trip produced correct results + assertNotNull(finalStatus, "Should have received COMPLETE status before timeout"); + assertEquals(OptJobStatus.COMPLETE, finalStatus.getStatus()); + assertNotNull(finalStatus.getResults(), "COMPLETE status should include results"); + assertNotNull(finalStatus.getResults().getOptResultSet(), "Results should have optResultSet"); + assertEquals(0.001, finalStatus.getResults().getOptResultSet().getObjectiveFunction(), 0.0001); + + var params = finalStatus.getResults().getOptResultSet().getOptParameterValues(); + assertNotNull(params); + assertEquals(1.5, params.get("k1"), 0.001); + assertEquals(2.5, params.get("k2"), 0.001); + } + + private org.vcell.restclient.model.OptProblem createClientOptProblem() { + var optProblem = new org.vcell.restclient.model.OptProblem(); + optProblem.setMathModelSbmlContents("test"); + optProblem.setNumberOfOptimizationRuns(1); + + var p1 = new org.vcell.restclient.model.ParameterDescription(); + p1.setName("k1"); + p1.setMinValue(0.0); + p1.setMaxValue(10.0); + p1.setInitialValue(1.0); + p1.setScale(1.0); + + var p2 = new org.vcell.restclient.model.ParameterDescription(); + p2.setName("k2"); + p2.setMinValue(0.0); + p2.setMaxValue(10.0); + p2.setInitialValue(2.0); + p2.setScale(1.0); + + optProblem.setParameterDescriptionList(java.util.List.of(p1, p2)); + optProblem.setDataSet(java.util.List.of( + java.util.List.of(0.0, 1.0, 2.0), + java.util.List.of(1.0, 1.5, 2.5) + )); + + var rv1 = new org.vcell.restclient.model.ReferenceVariable(); + rv1.setVarName("t"); + rv1.setReferenceVariableType(org.vcell.restclient.model.ReferenceVariableReferenceVariableType.INDEPENDENT); + var rv2 = new org.vcell.restclient.model.ReferenceVariable(); + rv2.setVarName("x"); + rv2.setReferenceVariableType(org.vcell.restclient.model.ReferenceVariableReferenceVariableType.DEPENDENT); + + optProblem.setReferenceVariable(java.util.List.of(rv1, rv2)); + + var method = new org.vcell.restclient.model.CopasiOptimizationMethod(); + method.setOptimizationMethodType(org.vcell.restclient.model.CopasiOptimizationMethodOptimizationMethodType.EVOLUTIONARYPROGRAM); + method.setOptimizationParameter(java.util.List.of()); + optProblem.setCopasiOptimizationMethod(method); + + return optProblem; + } +} diff --git a/vcell-rest/src/test/java/org/vcell/restq/testresources/ArtemisTestResource.java b/vcell-rest/src/test/java/org/vcell/restq/testresources/ArtemisTestResource.java new file mode 100644 index 0000000000..a223d80bd3 --- /dev/null +++ b/vcell-rest/src/test/java/org/vcell/restq/testresources/ArtemisTestResource.java @@ -0,0 +1,48 @@ +package org.vcell.restq.testresources; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; + +import java.util.Map; + +/** + * Starts an Artemis broker testcontainer with both AMQP 1.0 (5672) and OpenWire (61616) ports exposed. + * Replaces Quarkus AMQP devservices so that cross-protocol tests can connect via OpenWire JMS + * (the same protocol vcell-submit uses in production). + */ +public class ArtemisTestResource implements QuarkusTestResourceLifecycleManager { + + private GenericContainer artemis; + + @Override + public Map start() { + artemis = new GenericContainer<>("quay.io/artemiscloud/activemq-artemis-broker:1.0.25") + .withExposedPorts(5672, 61616) + .withEnv("AMQ_USER", "guest") + .withEnv("AMQ_PASSWORD", "guest") + .waitingFor(Wait.forListeningPort()); + artemis.start(); + + String host = artemis.getHost(); + int amqpPort = artemis.getMappedPort(5672); + int openwirePort = artemis.getMappedPort(61616); + + return Map.of( + "amqp-host", host, + "amqp-port", String.valueOf(amqpPort), + "amqp-username", "guest", + "amqp-password", "guest", + "quarkus.amqp.devservices.enabled", "false", + "artemis.openwire.host", host, + "artemis.openwire.port", String.valueOf(openwirePort) + ); + } + + @Override + public void stop() { + if (artemis != null) { + artemis.stop(); + } + } +} diff --git a/vcell-rest/src/test/java/org/vcell/restq/testresources/OpenWireOptSubmitStub.java b/vcell-rest/src/test/java/org/vcell/restq/testresources/OpenWireOptSubmitStub.java new file mode 100644 index 0000000000..5ddc9e9f7c --- /dev/null +++ b/vcell-rest/src/test/java/org/vcell/restq/testresources/OpenWireOptSubmitStub.java @@ -0,0 +1,201 @@ +package org.vcell.restq.testresources; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.activemq.ActiveMQConnectionFactory; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.vcell.optimization.OptJobStatus; +import org.vcell.optimization.OptRequestMessage; +import org.vcell.optimization.OptStatusMessage; +import org.vcell.optimization.jtd.*; + +import javax.jms.Connection; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Session; +import javax.jms.TextMessage; +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Test stub that mimics vcell-submit's OptimizationBatchServer.initOptimizationQueue() + * using the same OpenWire JMS protocol (ActiveMQConnectionFactory). + * + * Mirrors the real handleSubmitRequest() flow: + * 1. Receive OptRequestMessage from opt-request queue + * 2. Validate jobId is numeric + * 3. Read the OptProblem file from the path in the message (validates filesystem handoff) + * 4. Write result files to the paths from the message + * 5. Send QUEUED status back on opt-status queue with htcJobId + * + * The only difference from production is that instead of submitting to SLURM, + * the stub writes result files directly (simulating what the SLURM job would produce). + */ +public class OpenWireOptSubmitStub { + private static final Logger lg = LogManager.getLogger(OpenWireOptSubmitStub.class); + + private final String host; + private final int port; + private final ObjectMapper objectMapper = new ObjectMapper(); + private Connection connection; + private final CountDownLatch ready = new CountDownLatch(1); + + public OpenWireOptSubmitStub(String host, int port) { + this.host = host; + this.port = port; + } + + public void start() throws Exception { + // Same connection setup as OptimizationBatchServer.initOptimizationQueue() + ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("tcp://" + host + ":" + port); + factory.setTrustAllPackages(true); + Connection conn = factory.createConnection(); + conn.start(); + this.connection = conn; + + Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + Destination requestQueue = session.createQueue("opt-request"); + Destination statusQueue = session.createQueue("opt-status"); + MessageConsumer consumer = session.createConsumer(requestQueue); + MessageProducer producer = session.createProducer(statusQueue); + + Thread listenerThread = new Thread(() -> { + ready.countDown(); + while (!Thread.currentThread().isInterrupted()) { + try { + Message msg = consumer.receive(1000); + if (msg instanceof TextMessage textMsg) { + OptRequestMessage req = objectMapper.readValue(textMsg.getText(), OptRequestMessage.class); + lg.info("Stub received: command={}, jobId={}", req.command, req.jobId); + if ("submit".equals(req.command)) { + handleSubmitRequest(req, session, producer); + } else if ("stop".equals(req.command)) { + lg.info("Stub received stop for job {} (no-op in test)", req.jobId); + } else { + lg.warn("Stub: unknown command: {}", req.command); + } + } + } catch (JMSException e) { + if (!Thread.currentThread().isInterrupted()) { + lg.error("Stub listener error: {}", e.getMessage()); + } + break; + } catch (Exception e) { + lg.error("Stub processing error: {}", e.getMessage(), e); + } + } + }, "openwire-opt-stub"); + listenerThread.setDaemon(true); + listenerThread.start(); + + if (!ready.await(10, TimeUnit.SECONDS)) { + throw new RuntimeException("Stub listener did not start in time"); + } + } + + /** + * Mirrors OptimizationBatchServer.handleSubmitRequest() — same validation, same file I/O, + * same status message pattern. Only SLURM submission is replaced with direct file writes. + */ + private void handleSubmitRequest(OptRequestMessage request, Session session, MessageProducer producer) { + try { + // Validate jobId is numeric (same as real handleSubmitRequest) + Long.parseLong(request.jobId); + + // Read the OptProblem file from the path in the message + // (validates that vcell-rest wrote it to the correct location) + File optProblemFile = new File(request.optProblemFilePath); + OptProblem optProblem = objectMapper.readValue(optProblemFile, OptProblem.class); + lg.info("Stub: read OptProblem from {}, params={}", + optProblemFile.getName(), optProblem.getParameterDescriptionList().size()); + + // Use file paths from the message (same as real code uses request.optOutputFilePath, etc.) + File optOutputFile = new File(request.optOutputFilePath); + File optReportFile = new File(request.optReportFilePath); + + // Write progress report file (simulates what the SLURM CopasiParest job writes) + writeProgressReport(optReportFile); + + // Write result file (simulates SLURM job completion) + writeResults(optOutputFile); + + lg.info("Stub: wrote results for job {}", request.jobId); + + // Send QUEUED status back with htcJobId (same as real handleSubmitRequest) + sendStatusMessage(session, producer, request.jobId, OptJobStatus.QUEUED, null, "SLURM:99999"); + + } catch (Exception e) { + lg.error("Stub: failed to process job {}: {}", request.jobId, e.getMessage(), e); + try { + // Send FAILED status (same error handling as real handleSubmitRequest) + sendStatusMessage(session, producer, request.jobId, OptJobStatus.FAILED, e.getMessage(), null); + } catch (JMSException jmsEx) { + lg.error("Stub: failed to send FAILED status: {}", jmsEx.getMessage(), jmsEx); + } + } + } + + private void writeProgressReport(File reportFile) throws Exception { + reportFile.getParentFile().mkdirs(); + java.nio.file.Files.writeString(reportFile.toPath(), + "[\"k1\",\"k2\"]\n" + + "10\t0.5\t1.0\t2.0\n" + + "20\t0.1\t1.3\t2.4\n" + + "30\t0.01\t1.5\t2.5\n"); + } + + private void writeResults(File outputFile) throws Exception { + Vcellopt vcellopt = new Vcellopt(); + vcellopt.setStatus(VcelloptStatus.COMPLETE); + vcellopt.setStatusMessage("optimization complete"); + OptResultSet resultSet = new OptResultSet(); + resultSet.setNumFunctionEvaluations(30); + resultSet.setObjectiveFunction(0.001); + resultSet.setOptParameterValues(Map.of("k1", 1.5, "k2", 2.5)); + OptProgressReport progressReport = new OptProgressReport(); + progressReport.setBestParamValues(Map.of("k1", 1.5, "k2", 2.5)); + OptProgressItem item1 = new OptProgressItem(); + item1.setNumFunctionEvaluations(10); + item1.setObjFuncValue(0.5); + OptProgressItem item2 = new OptProgressItem(); + item2.setNumFunctionEvaluations(20); + item2.setObjFuncValue(0.1); + OptProgressItem item3 = new OptProgressItem(); + item3.setNumFunctionEvaluations(30); + item3.setObjFuncValue(0.01); + progressReport.setProgressItems(List.of(item1, item2, item3)); + resultSet.setOptProgressReport(progressReport); + vcellopt.setOptResultSet(resultSet); + objectMapper.writeValue(outputFile, vcellopt); + } + + /** + * Same signature and logic as OptimizationBatchServer.sendStatusMessage(). + */ + private void sendStatusMessage(Session session, MessageProducer producer, + String jobId, OptJobStatus status, String statusMessage, String htcJobId) + throws JMSException { + try { + OptStatusMessage statusMsg = new OptStatusMessage(jobId, status, statusMessage, htcJobId); + String json = objectMapper.writeValueAsString(statusMsg); + TextMessage textMessage = session.createTextMessage(json); + producer.send(textMessage); + lg.info("Stub sent status: jobId={}, status={}", jobId, status); + } catch (Exception e) { + lg.error("Stub: failed to send status for job {}: {}", jobId, e.getMessage(), e); + throw new JMSException("Failed to serialize status message: " + e.getMessage()); + } + } + + public void stop() throws Exception { + if (connection != null) { + connection.close(); + } + } +} From b66c49d9a79b0f61961257dbd2eeda5b8de15e6c Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Fri, 10 Apr 2026 00:20:01 -0400 Subject: [PATCH 27/40] Fix CI: remove unused ActiveMQ import from QuarkusStartUpTasks Stale import of org.apache.activemq.ActiveMQConnectionFactory fails compile when activemq-client is only in test scope. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/main/java/org/vcell/restq/QuarkusStartUpTasks.java | 1 - 1 file changed, 1 deletion(-) diff --git a/vcell-rest/src/main/java/org/vcell/restq/QuarkusStartUpTasks.java b/vcell-rest/src/main/java/org/vcell/restq/QuarkusStartUpTasks.java index 9216547889..3cfc1fe34c 100644 --- a/vcell-rest/src/main/java/org/vcell/restq/QuarkusStartUpTasks.java +++ b/vcell-rest/src/main/java/org/vcell/restq/QuarkusStartUpTasks.java @@ -8,7 +8,6 @@ import jakarta.enterprise.event.Observes; import jakarta.inject.Inject; import jakarta.ws.rs.Produces; -import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.vcell.restq.db.AgroalConnectionFactory; From 1897d7fece4c951e6c98956326baea3ae10ccf68 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Fri, 10 Apr 2026 07:05:53 -0400 Subject: [PATCH 28/40] Fix progress reporting for all active optimization states Server now reads the progress report file for SUBMITTED/QUEUED states (not just RUNNING), and auto-promotes to RUNNING when progress appears on disk. Client now dispatches progress to the UI for all active states, so the objective function graph and best parameter values update as soon as the SLURM solver starts writing. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../java/copasi/CopasiOptimizationSolverRemote.java | 10 ++++------ .../restq/services/OptimizationRestService.java | 12 +++++++++--- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java b/vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java index b6ce60ad5c..45b2650699 100644 --- a/vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java +++ b/vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java @@ -118,11 +118,6 @@ public static OptimizationResultSet solveRemoteApi( switch (status.getStatus()) { case SUBMITTED: case QUEUED: - if (clientTaskStatusSupport != null) { - clientTaskStatusSupport.setMessage("Queued..."); - } - break; - case RUNNING: if (status.getProgressReport() != null) { String progressJson = objectMapper.writeValueAsString(status.getProgressReport()); @@ -140,7 +135,10 @@ public static OptimizationResultSet solveRemoteApi( }); } else { if (clientTaskStatusSupport != null) { - clientTaskStatusSupport.setMessage("Running (waiting for progress) ..."); + clientTaskStatusSupport.setMessage( + status.getStatus() == org.vcell.restclient.model.OptJobStatus.RUNNING + ? "Running (waiting for progress) ..." + : "Queued..."); } } break; diff --git a/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java b/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java index 9d5f3394bf..34239cc4a9 100644 --- a/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java +++ b/vcell-rest/src/main/java/org/vcell/restq/services/OptimizationRestService.java @@ -99,9 +99,12 @@ public OptimizationJobStatus getOptimizationStatus(KeyValue jobKey, User user) Vcellopt results = null; switch (record.status()) { + case SUBMITTED: case RUNNING: case QUEUED: - // Try to read progress from the report file + // Try to read progress from the report file. + // Even in SUBMITTED state, the SLURM job may already be running and writing progress + // (the QUEUED status message from vcell-submit arrives asynchronously). progressReport = readProgressReport(record.optReportFile()); // Check if the output file has appeared (solver completed) results = readResults(record.optOutputFile()); @@ -109,6 +112,11 @@ public OptimizationJobStatus getOptimizationStatus(KeyValue jobKey, User user) updateOptJobStatus(jobKey, OptJobStatus.COMPLETE, null); return new OptimizationJobStatus(jobKey, OptJobStatus.COMPLETE, null, record.htcJobId(), progressReport, results); } + // Auto-promote to RUNNING if progress report exists and we're still in SUBMITTED/QUEUED + if (progressReport != null && record.status() != OptJobStatus.RUNNING) { + updateOptJobStatus(jobKey, OptJobStatus.RUNNING, null); + return new OptimizationJobStatus(jobKey, OptJobStatus.RUNNING, null, record.htcJobId(), progressReport, null); + } break; case COMPLETE: progressReport = readProgressReport(record.optReportFile()); @@ -119,8 +127,6 @@ public OptimizationJobStatus getOptimizationStatus(KeyValue jobKey, User user) progressReport = readProgressReport(record.optReportFile()); break; case FAILED: - case SUBMITTED: - // No progress or results expected break; } From 4f157bc81e44c0e5ecf208de0ed020c2b9e44933 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Fri, 10 Apr 2026 07:05:59 -0400 Subject: [PATCH 29/40] Update parameter estimation design doc with deployment learnings Rewrite architecture diagram to show Artemis cross-protocol flow and filesystem-driven status promotion. Add sections on cross-protocol messaging pitfalls, real-time progress reporting, and message types. Replace implementation plan with completed work and remaining items. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/parameter-estimation-service.md | 414 +++++++++++++++++---------- 1 file changed, 260 insertions(+), 154 deletions(-) diff --git a/docs/parameter-estimation-service.md b/docs/parameter-estimation-service.md index 08a607301a..9240bca3c6 100644 --- a/docs/parameter-estimation-service.md +++ b/docs/parameter-estimation-service.md @@ -37,48 +37,133 @@ Desktop Client / webapp v vcell-rest (Quarkus) | 1. Validate OptProblem - | 2. Generate UUID job ID + | 2. Get bigint job ID from database sequence (newSeq) | 3. Write OptProblem JSON to NFS: /simdata/parest_data/CopasiParest_{id}_optProblem.json | 4. Insert vc_optjob row (status=SUBMITTED) - | 5. Send ActiveMQ message to vcell-submit + | 5. Publish OptRequestMessage to Artemis "opt-request" queue (AMQP 1.0 via SmallRye) | 6. Return OptimizationJobStatus to client v -ActiveMQ (activemqint) - | +Artemis broker (artemismq:61616) + | ANYCAST queue "opt-request" + | Cross-protocol: AMQP 1.0 (vcell-rest) ↔ OpenWire JMS (vcell-submit) v -vcell-submit - | 1. Receive message - | 2. Submit SLURM job via SlurmProxy (reuses existing code) - | 3. Send status update message back (QUEUED + htcJobId, or FAILED + error) +vcell-submit (OpenWire JMS via ActiveMQConnectionFactory) + | 1. Consume OptRequestMessage from "opt-request" queue + | 2. Read OptProblem file from NFS path in message + | 3. Submit SLURM job via SlurmProxy.submitOptimizationJob() + | 4. Publish OptStatusMessage to "opt-status" queue (QUEUED + htcJobId, or FAILED + error) v SLURM → Singularity container → vcell-opt Python solver (UNCHANGED) | Writes to NFS: | - CopasiParest_{id}_optReport.txt (progress, written incrementally) | - CopasiParest_{id}_optRun.json (final results) v +Artemis broker + | ANYCAST queue "opt-status" + | vcell-submit publishes via OpenWire JMS + | vcell-rest consumes via AMQP 1.0 (SmallRye @Incoming) + v +vcell-rest (OptimizationMQ.consumeOptStatus) + | Updates vc_optjob: status=QUEUED, htcJobId from message + v vcell-rest (polling on client request) - | 1. Client polls GET /api/v1/optimization/{id} + | 1. Client polls GET /api/v1/optimization/{id} every 2 seconds | 2. Check vc_optjob status in database - | 3. If RUNNING: read progress from report file on NFS (CopasiUtils.readProgressReportFromCSV) - | 4. If COMPLETE: read results from output file on NFS - | 5. Return OptimizationJobStatus with progress/results + | 3. For any active status (SUBMITTED/QUEUED/RUNNING): + | a. Read progress from report file on NFS (CopasiUtils.readProgressReportFromCSV) + | b. If progress exists and status is SUBMITTED/QUEUED: auto-promote to RUNNING + | c. If result file exists: auto-promote to COMPLETE, read Vcellopt results + | 4. Return OptimizationJobStatus with progress/results v -Client displays progress or results +Client displays progress (objective function vs iteration graph, best parameter values) ``` +### Cross-protocol messaging through Artemis + +The optimization messaging uses **cross-protocol communication** through an Apache Artemis broker. This is a critical architectural detail that has been a source of bugs. + +**Protocol mapping:** +- **vcell-rest** uses **AMQP 1.0** via Quarkus SmallRye Reactive Messaging (`quarkus-smallrye-reactive-messaging-amqp`) +- **vcell-submit** uses **OpenWire JMS** via ActiveMQ 5.x client (`org.apache.activemq.ActiveMQConnectionFactory`) +- **Artemis** accepts both protocols on port 61616 (all-protocol acceptor) and routes messages between them + +**Queue configuration:** +- `opt-request` — ANYCAST queue. vcell-rest produces (AMQP 1.0), vcell-submit consumes (OpenWire JMS) +- `opt-status` — ANYCAST queue. vcell-submit produces (OpenWire JMS), vcell-rest consumes (AMQP 1.0) + +**Critical SmallRye AMQP settings** (in `application.properties`): + +```properties +mp.messaging.outgoing.publisher-opt-request.connector=smallrye-amqp +mp.messaging.outgoing.publisher-opt-request.address=opt-request +mp.messaging.outgoing.publisher-opt-request.capabilities=queue + +mp.messaging.incoming.subscriber-opt-status.connector=smallrye-amqp +mp.messaging.incoming.subscriber-opt-status.address=opt-status +mp.messaging.incoming.subscriber-opt-status.capabilities=queue +``` + +**Lessons learned from deployment bugs:** + +1. **`address` is required.** Without explicit `address=opt-request`, SmallRye defaults to using the channel name (`publisher-opt-request`) as the AMQP address. The OpenWire consumer listens on queue `opt-request`, so messages are lost silently. The broker accepts and acknowledges the message (the AMQP disposition shows `Accepted`), but no consumer ever sees it. + +2. **`capabilities=queue` is required.** Artemis deploys queues as ANYCAST (point-to-point) by default, which is what OpenWire `session.createQueue()` creates. But SmallRye AMQP 1.0 consumers default to MULTICAST (pub-sub) semantics when attaching without specifying capabilities. This causes Artemis to create a separate MULTICAST subscription that never receives messages from the ANYCAST queue. The `capabilities=queue` annotation tells the AMQP client to attach with the "queue" capability, which Artemis interprets as ANYCAST. + +3. **vcell-submit needs Artemis connection properties.** The `vcell.jms.artemis.host.internal` and `vcell.jms.artemis.port.internal` Java system properties must be set in the vcell-submit Dockerfile and K8s deployment. These are separate from the existing `activemqint` connection used for simulation job dispatch. + +**K8s configuration** (in vcell-fluxcd `shared.env`): +``` +jmshost_artemis_internal=artemismq +jmsport_artemis_internal=61616 +``` + +These are consumed by: +- vcell-rest: `application.properties` `%prod.amqp-host=${jmshost_artemis_internal}` (AMQP connection) +- vcell-submit: `Dockerfile-submit-dev` `-Dvcell.jms.artemis.host.internal="${jmshost_artemis_internal}"` (OpenWire connection) + ### Key design decisions **Database-backed job tracking.** Every optimization job gets a row in `vc_optjob`. The database is the source of truth for job lifecycle state (SUBMITTED → QUEUED → RUNNING → COMPLETE/FAILED/STOPPED). This survives pod restarts, supports multiple API replicas, and provides an audit trail. **Filesystem for data, database for state.** The OptProblem input, result output, and progress report are files on NFS — this matches the Python solver's file-based interface and avoids putting large blobs in the database. The database tracks job metadata and status; the filesystem holds the actual data. -**ActiveMQ for job dispatch (fire-and-forget).** vcell-rest sends a message to vcell-submit to trigger SLURM submission, and vcell-submit sends a status message back. This replaces the persistent TCP socket with a reliable message broker that already exists in the deployment (`activemqint`). The messages are small (job ID + file paths), and the pattern follows the existing `ExportRequestListenerMQ` in vcell-rest. +**Artemis for job dispatch (cross-protocol).** vcell-rest sends a message to vcell-submit via Artemis to trigger SLURM submission, and vcell-submit sends a status message back. This replaces the persistent TCP socket with a durable message broker. The messages are small JSON payloads (job ID + file paths). The cross-protocol design (AMQP 1.0 ↔ OpenWire JMS) is necessary because vcell-rest uses Quarkus SmallRye AMQP while vcell-submit uses the ActiveMQ 5.x OpenWire client. **Database-sequence job IDs.** Replace the random 0–999,999 integer with `bigint` keys from the shared `newSeq` database sequence, consistent with every other VCell table (`vc_biomodel`, `vc_simulation`, etc.). Uses the existing `KeyValue` type and `KeyFactory.getNewKey()` infrastructure. **Progress reporting via filesystem polling.** The Python solver already writes a TSV report file incrementally as COPASI iterates. vcell-rest reads this file directly from NFS using `CopasiUtils.readProgressReportFromCSV()` (in `vcell-core`, already a dependency). This eliminates the batch server as a middleman for progress data. Each row contains: function evaluation count, best objective value, and best parameter vector. -**User-initiated stop via messaging.** When a user determines the optimization has sufficiently converged and stops the job, vcell-rest sends a stop message (with the SLURM job ID from the database) via ActiveMQ to vcell-submit, which calls `killJobSafe()` → `scancel`. The report file retains all progress up to the kill point, so the client can read the best parameters found. +**Filesystem-driven status promotion.** vcell-submit only sends a single `QUEUED` status message back after submitting the SLURM job. All subsequent status transitions are driven by vcell-rest reading the filesystem on each client poll: +- **SUBMITTED/QUEUED → RUNNING**: when the progress report file appears on NFS (meaning the SLURM job has started writing) +- **RUNNING → COMPLETE**: when the result output file appears on NFS (meaning the solver finished) + +This design avoids the need for vcell-submit to monitor SLURM job state and send incremental status updates. The filesystem is the source of truth for solver progress, and the database tracks the lifecycle state. + +**User-initiated stop via messaging.** When a user determines the optimization has sufficiently converged and stops the job, vcell-rest sends a stop message (with the SLURM job ID from the database) via Artemis to vcell-submit, which calls `killJobSafe()` → `scancel`. The report file retains all progress up to the kill point, so the client can read the best parameters found. + +### Real-time progress reporting + +The desktop client displays a real-time graph of objective function value vs. function evaluations, along with the current best parameter values. This updates every 2 seconds as the solver runs. + +**How it works:** + +1. The Python COPASI solver writes a TSV report file incrementally (`CopasiParest_{id}_optReport.txt`) with one row per sampled iteration. Format: + ``` + ["k1","k2"] ← header: JSON array of parameter names + 10 0.5 1.0 2.0 ← numEvals, objFuncValue, param1, param2, ... + 20 0.1 1.3 2.4 + 30 0.01 1.5 2.5 + ``` + +2. On each client poll, `OptimizationRestService.getOptimizationStatus()` reads this file via `CopasiUtils.readProgressReportFromCSV()`, which returns an `OptProgressReport` containing: + - `progressItems`: list of `OptProgressItem` (numFunctionEvaluations, objFuncValue) — one per sampled iteration + - `bestParamValues`: map of parameter name → best value found so far + +3. The client receives the `OptProgressReport` in the `OptimizationJobStatus` response and dispatches it to `CopasiOptSolverCallbacks.setProgressReport()`, which fires a `PropertyChangeEvent` to the UI. + +4. `RunStatusProgressDialog` (in `ParameterEstimationRunTaskPanel`) listens for these events and updates the objective function vs. evaluations plot and the best parameter table. + +**Key design choice:** Progress is read from the filesystem on every poll, not sent through the message queue. This means progress is available as soon as the SLURM job starts writing, even before the `QUEUED` status message arrives from vcell-submit. The server auto-promotes the status from `SUBMITTED`/`QUEUED` → `RUNNING` when it detects progress data on disk. This replicates the behavior of the legacy socket-based design where every status query re-read the report file. ### Database schema @@ -121,19 +206,22 @@ SUBMITTED → QUEUED → RUNNING → COMPLETE ``` POST /api/v1/optimization Submit optimization job +GET /api/v1/optimization List user's optimization jobs GET /api/v1/optimization/{id} Get job status, progress, or results POST /api/v1/optimization/{id}/stop Stop a running job ``` -**POST /api/v1/optimization** — Requires authenticated user. Accepts `OptProblem` JSON body. Writes input file to NFS, creates database record, sends dispatch message. Returns `OptimizationJobStatus` with the job ID and status=SUBMITTED. +**POST /api/v1/optimization** — Requires authenticated user. Accepts `OptProblem` JSON body. Writes input file to NFS, creates database record, publishes dispatch message to Artemis. Returns `OptimizationJobStatus` with the job ID and status=SUBMITTED. + +**GET /api/v1/optimization** — Requires authenticated user. Returns array of `OptimizationJobStatus` for the user's jobs, most recent first. Lightweight (no progress/results). **GET /api/v1/optimization/{id}** — Requires authenticated user (must be job owner). Returns `OptimizationJobStatus` which includes: - `status` — current job state -- `progressReport` — (when RUNNING) iteration count, objective value, best parameters from the report file +- `progressReport` — (when RUNNING/QUEUED) iteration count, objective value, best parameters from the report file - `results` — (when COMPLETE) the full `Vcellopt` result - `statusMessage` — (when FAILED/STOPPED) error or cancellation description -**POST /api/v1/optimization/{id}/stop** — Requires authenticated user (must be job owner). Sends stop message to vcell-submit, updates database status to STOPPED. The client can then GET the job to read the last progress report for the best parameters found. +**POST /api/v1/optimization/{id}/stop** — Requires authenticated user (must be job owner). Sends stop message to vcell-submit via Artemis, updates database status to STOPPED. The client can then GET the job to read the last progress report for the best parameters found. ### Response DTO @@ -142,6 +230,7 @@ public record OptimizationJobStatus( KeyValue id, OptJobStatus status, String statusMessage, + String htcJobId, OptProgressReport progressReport, Vcellopt results ) {} @@ -149,6 +238,28 @@ public record OptimizationJobStatus( The client checks `status` and reads the appropriate nullable field. This avoids the current string-prefix parsing pattern (`"QUEUED:"`, `"RUNNING:"`, etc.). +### Message types + +Shared in `vcell-core` (`org.vcell.optimization` package), serialized as JSON: + +```java +public class OptRequestMessage { + public String jobId; + public String command; // "submit" or "stop" + public String optProblemFilePath; // for submit + public String optOutputFilePath; // for submit + public String optReportFilePath; // for submit + public String htcJobId; // for stop (SLURM job to cancel) +} + +public class OptStatusMessage { + public String jobId; + public OptJobStatus status; + public String statusMessage; + public String htcJobId; // set when SLURM job is submitted +} +``` + ## Desktop client architecture (current) The desktop client has a layered architecture for parameter estimation: @@ -161,12 +272,13 @@ The desktop client has a layered architecture for parameter estimation: ### Solver coordination layer -- **`CopasiOptimizationSolverRemote`** (`vcell-client/.../copasi/CopasiOptimizationSolverRemote.java`) — Orchestrates the remote optimization call. The `solveRemoteApi()` method (line 27): +- **`CopasiOptimizationSolverRemote`** (`vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java`) — Orchestrates the remote optimization call. The `solveRemoteApi()` method: 1. Converts `ParameterEstimationTask` → `OptProblem` via `CopasiUtils.paramTaskToOptProblem()` - 2. Calls `apiClient.submitOptimization(optProblemJson)` → gets back optimization ID - 3. Polls `apiClient.getOptRunJson(optId, bStopRequested)` every ~2 seconds - 4. Parses string-prefixed responses (`"QUEUED:"`, `"RUNNING:"`, etc.) and raw JSON for `OptProgressReport` and `Vcellopt` - 5. Updates `CopasiOptSolverCallbacks` with progress, which fires property change events to the UI + 2. Converts to generated client model type and calls `optApi.submitOptimization(optProblem)` + 3. Polls `optApi.getOptimizationStatus(jobId)` every 2 seconds with a 200-second timeout + 4. Uses typed `OptimizationJobStatus` fields (`status`, `progressReport`, `results`) instead of string-prefix parsing + 5. Updates `CopasiOptSolverCallbacks` with progress via a pluggable `progressDispatcher` (SwingUtilities::invokeLater in GUI, Runnable::run in tests) + 6. Handles user stop via `optApi.stopOptimization(jobId)` ### Callback / event layer @@ -174,125 +286,95 @@ The desktop client has a layered architecture for parameter estimation: ### API client layer -- **`VCellApiClient`** (`vcell-apiclient/.../api/client/VCellApiClient.java`) — HTTP client for the legacy `/api/v0/` endpoints: - - `submitOptimization()` (line 235) — POST to `/api/v0/optimization`, returns optimization ID as plain text - - `getOptRunJson()` (line 219) — GET to `/api/v0/optimization/{id}?bStop={bStop}`, returns raw JSON string that the caller must parse by inspecting string prefixes - -- **`ClientServerManager`** (`vcell-apiclient/.../server/ClientServerManager.java`) — Provides the `VCellApiClient` instance to the desktop client. - -### What changes in the migration - -The new auto-generated `OptimizationResourceApi` (from `vcell-restclient`) replaces `VCellApiClient` for optimization. The key improvements: - -1. **Typed responses.** `getOptimizationStatus()` returns `OptimizationJobStatus` with explicit `status`, `progressReport`, and `results` fields — replacing the error-prone string-prefix parsing in `CopasiOptimizationSolverRemote`. -2. **Separate stop endpoint.** `POST /{id}/stop` replaces the `bStop` query parameter hack on the GET endpoint. -3. **Auto-generated.** The Java client is generated from the OpenAPI spec, so it stays in sync with the server automatically. - -The UI layer (`ParameterEstimationRunTaskPanel`, `RunStatusProgressDialog`) and the callback layer (`CopasiOptSolverCallbacks`) are **unchanged** — they already consume `OptProgressReport` and `Vcellopt` objects, which are the same types the new API returns. - -## Implementation plan - -### Commit 1: Database schema and service layer - -- Add `vc_optjob` table to `vcell-rest/src/main/resources/scripts/init.sql` -- Create `OptimizationJobStatus` DTO in `vcell-rest` -- Create `OptimizationRestService` (`@ApplicationScoped`) with database CRUD operations and filesystem read methods -- Oracle migration DDL script for production - -### Commit 2: REST endpoints - -- Create `OptimizationResource.java` in `vcell-rest/src/main/java/org/vcell/restq/handlers/` -- Endpoints: submit, status, stop -- Authentication and ownership checks -- Follow patterns from `SimulationResource.java` - -### Commit 3: ActiveMQ messaging (vcell-rest side) - -- Add AMQP channel configuration to `application.properties` -- Create `OptimizationMQProducer` for sending submit/stop commands -- Create `OptimizationMQConsumer` for receiving status updates from vcell-submit -- Update `vc_optjob` status on incoming messages - -### Commit 4: ActiveMQ messaging (vcell-submit side) - -- Add JMS queue listener in `HtcSimulationWorker` for optimization requests -- On "submit": call `SlurmProxy.submitOptimizationJob()` (existing code), send back QUEUED + htcJobId -- On "stop": call `killJobSafe()` with SLURM job ID from message -- Reuse existing `OptimizationBatchServer.submitOptProblem()` logic - -### Commit 5: Tests (three levels) - -Three levels of integration testing, each building on the previous: - -#### Level 1: REST + DB + filesystem — `@Tag("Quarkus")` - -Runs in CI. Uses testcontainers for PostgreSQL and Keycloak (already configured for other Quarkus tests). The ActiveMQ producer is mocked. Simulates solver completion by writing fake report/output files to a temp directory. - -Create `OptimizationApiTest.java` (`@QuarkusTest`): -- Test submit: POST OptProblem, verify DB record created, verify OptProblem file written to disk -- Test status polling: write mock report file with progress rows, call GET, verify `OptimizationJobStatus` contains correct `OptProgressReport` (iteration count, objective value, best parameters) -- Test completion: write mock output file, call GET, verify status=COMPLETE with `Vcellopt` results -- Test stop: POST stop, verify DB status updated to STOPPED -- Test authorization: verify user can only access their own jobs -- Test error: verify FAILED status with `statusMessage` error description - -#### Level 2: REST + DB + ActiveMQ round-trip — `@Tag("Quarkus")` - -Runs in CI. Adds an ActiveMQ testcontainer. Both the vcell-rest producer and a test-harness consumer run in the same JVM. The test consumer simulates vcell-submit: it receives the submit message, writes mock result files to the temp directory, and sends a status update message back. - -Create `OptimizationMQTest.java` (`@QuarkusTest`): -- Test submit → message received by consumer → status update sent back → DB updated to QUEUED with htcJobId -- Test stop → stop message received by consumer → DB updated to STOPPED -- Test failure → consumer sends FAILED status with error description → DB and GET endpoint reflect failure - -This tests the full messaging contract between vcell-rest and vcell-submit without needing SLURM. - -#### Level 3: Full end-to-end with SLURM — `@Tag("SLURM_IT")` - -Does NOT run in CI. Requires a developer on the network with NFS mounted and SSH access to the SLURM cluster. Configured via system properties: - -``` -vcell.test.slurm.host SLURM login node (e.g. login.hpc.cam.uchc.edu) -vcell.test.slurm.user SSH user for SLURM submission (e.g. vcell) -vcell.test.slurm.keypath Path to SSH private key (e.g. /path/to/id_rsa) -vcell.test.slurm.singularity.image Path to vcell-opt Singularity image on cluster -vcell.test.nfs.parestdir NFS path for optimization data, accessible from - both test machine and SLURM (e.g. /simdata/parest_data) -``` - -The test is skipped (via JUnit `Assumptions.assumeTrue`) if any required property is missing. - -Create `OptimizationSlurmIT.java` (`@QuarkusTest`): -- Uses testcontainers for PostgreSQL and ActiveMQ -- Includes a real submit-side handler (in-process) that SSHes to SLURM and submits the job using `SlurmProxy` -- Submits a small, fast-converging OptProblem (few parameters, few data points, low max iterations) to minimize SLURM runtime -- Polls the status endpoint with a generous timeout (5 minutes, to account for SLURM queue wait) -- Verifies that: - - Progress reports arrive with real iteration counts and objective function values - - The final result contains optimized parameter values - - The parameter values are reasonable (within expected bounds) -- Tests user-initiated stop: submit a longer-running problem, poll until RUNNING with progress, stop, verify report file has partial progress - -This test exercises the complete production path: REST → DB → ActiveMQ → SSH → SLURM → Singularity → Python/COPASI → NFS → filesystem polling → REST response. - -### Commit 6: Regenerate OpenAPI clients - -- Run `tools/generate.sh` -- Verify downstream: `mvn compile test-compile -pl vcell-rest -am` -- New `OptimizationResourceApi` class generated for Java, Python, TypeScript clients - -### Commit 7: Update desktop client - -- Modify `CopasiOptimizationSolverRemote.solveRemoteApi()` to use the auto-generated `OptimizationResourceApi` instead of `VCellApiClient` -- Replace string-prefix parsing with typed `OptimizationJobStatus` fields: - - `status` field replaces parsing for `"QUEUED:"`, `"RUNNING:"`, `"FAILED:"` prefixes - - `progressReport` field replaces JSON deserialization of embedded progress strings - - `results` field replaces JSON deserialization of the final `Vcellopt` -- Use `POST /{id}/stop` for stop requests instead of the `bStop` query parameter on GET -- The UI layer (`ParameterEstimationRunTaskPanel`, `RunStatusProgressDialog`) and the callback layer (`CopasiOptSolverCallbacks`) require no changes — they already consume `OptProgressReport` and `Vcellopt` objects - -### Commit 8: Remove legacy optimization code - +- **`OptimizationResourceApi`** (auto-generated in `vcell-restclient`) — Typed REST client for `/api/v1/optimization` endpoints. Generated from the OpenAPI spec via `tools/openapi-clients.sh`. + +- **`VCellApiClient`** (`vcell-apiclient/.../api/client/VCellApiClient.java`) — Provides access to the generated `OptimizationResourceApi` via `getOptimizationApi()`. + +## Implementation status + +### Completed (parest-bug branch) + +All commits listed below are on the `parest-bug` branch, ahead of `master`. + +#### Commit 1: Database schema and service layer +- `OptJobTable` in `vcell-core` with Oracle-compatible SQL generation +- `OptimizationRestService` (`@ApplicationScoped`) with database CRUD and filesystem read methods +- `OptimizationJobStatus` record DTO, `OptJobRecord`, `OptJobStatus` enum +- Bigint job IDs from database sequence via `KeyFactory.getNewKey()` + +#### Commit 2: REST endpoints +- `OptimizationResource.java` — submit, status, list, stop endpoints +- Authentication via `@RolesAllowed("user")`, ownership checks via user ID +- `POST /api/v1/optimization`, `GET /api/v1/optimization/{id}`, `GET /api/v1/optimization`, `POST /api/v1/optimization/{id}/stop` +- CodeQL path traversal fixes for optimization file paths + +#### Commit 3: ActiveMQ messaging (vcell-rest side) +- `OptimizationMQ.java` — SmallRye AMQP producer (`publisher-opt-request`) and consumer (`subscriber-opt-status`) +- `OptRequestMessage` and `OptStatusMessage` shared types in `vcell-core` +- AMQP channel config in `application.properties` with `capabilities=queue` for ANYCAST routing +- Artemis broker connection: `%prod.amqp-host`, `%prod.amqp-port` + +#### Commit 4: ActiveMQ messaging (vcell-submit side) +- `OptimizationBatchServer.initOptimizationQueue()` — OpenWire JMS listener on `opt-request` queue +- `handleSubmitRequest()` — reads OptProblem from NFS, submits to SLURM, sends QUEUED status back on `opt-status` +- `handleStopRequest()` — kills SLURM job via `HtcProxy.killJobSafe()` +- `sendStatusMessage()` — sends `OptStatusMessage` JSON on `opt-status` queue +- Path validation via `validateParestPath()` to prevent traversal +- Artemis connection configured via `vcell.jms.artemis.host.internal` / `vcell.jms.artemis.port.internal` system properties +- `Dockerfile-submit-dev` updated with Artemis env vars and `-D` flags + +#### Commit 5: Tests + +**Level 1: REST + DB + filesystem** (`OptimizationResourceTest.java`, `@Tag("Quarkus")`) +- 10 tests covering submit, status polling, completion detection, stop, authorization, error handling +- Uses testcontainers for PostgreSQL and Keycloak +- Simulates solver by writing mock report/output files to temp directory + +**Level 1.5: E2E client flow** (`OptimizationE2ETest.java`, `@Tag("Quarkus")`) +- Tests the same code path as `CopasiOptimizationSolverRemote.solveRemoteApi()` +- Uses the generated `OptimizationResourceApi` client +- Mock vcell-submit directly updates database (bypasses messaging) +- Tests submit+poll+complete and submit+stop flows + +**Level 2: Cross-protocol messaging** (`OptimizationCrossProtocolTest.java`, `@Tag("Quarkus")`) +- Full AMQP 1.0 ↔ OpenWire JMS round-trip through a real Artemis testcontainer +- `ArtemisTestResource` — `QuarkusTestResourceLifecycleManager` that starts Artemis with both AMQP (5672) and OpenWire (61616) ports +- `OpenWireOptSubmitStub` — mirrors `OptimizationBatchServer.handleSubmitRequest()` using `ActiveMQConnectionFactory` (same OpenWire protocol as production vcell-submit) +- Validates: address mapping, ANYCAST/MULTICAST routing, JSON serialization across protocols, filesystem handoff (stub reads OptProblem file written by vcell-rest) +- This test would have caught both production bugs (wrong AMQP address and MULTICAST subscription) + +#### Commit 6: OpenAPI client regeneration +- Consolidated `tools/generate.sh` + `tools/compile-and-build-clients.sh` into `tools/openapi-clients.sh` +- Regenerated Java (`vcell-restclient`), Python (`python-restclient`), TypeScript (`webapp-ng`) clients +- New `OptimizationResourceApi` class with `submitOptimization()`, `getOptimizationStatus()`, `stopOptimization()`, `listOptimizationJobs()` + +#### Commit 7: Desktop client update +- `CopasiOptimizationSolverRemote.solveRemoteApi()` rewritten to use generated `OptimizationResourceApi` +- Typed `OptimizationJobStatus` fields replace string-prefix parsing +- `POST /{id}/stop` replaces `bStop` query parameter +- Testable overload accepts `OptimizationResourceApi` and `Consumer` dispatcher directly (no Swing dependency) +- 200-second timeout, 2-second poll interval + +#### Deployment fixes (discovered during dev deployment) +- AMQP address mapping: added `address=opt-request` / `address=opt-status` to production channel config (without this, SmallRye sent to channel name instead of queue name) +- ANYCAST routing: added `capabilities=queue` to all AMQP channel configs (without this, SmallRye created MULTICAST subscriptions that missed OpenWire messages) +- Artemis connection for vcell-submit: added `vcell.jms.artemis.host.internal` / `vcell.jms.artemis.port.internal` to `Dockerfile-submit-dev` +- JDBC resource leak: wrapped Statement/ResultSet in try-with-resources in `getOptJobRecord()` and `listOptimizationJobs()` + +#### Progress reporting fix +- Server: `SUBMITTED` status now reads the progress report file (the SLURM job may already be running before the async QUEUED message arrives). Auto-promotes SUBMITTED/QUEUED → RUNNING when progress data appears on disk. +- Client: `SUBMITTED`/`QUEUED`/`RUNNING` all dispatch progress to `CopasiOptSolverCallbacks.setProgressReport()` when available, so the objective function graph and best parameter values update as soon as the solver starts writing. + +### Remaining work + +#### Deploy and validate on dev +- Deploy latest release to dev (vcell-dev.cam.uchc.edu) +- Test full round-trip: desktop client → vcell-rest → Artemis → vcell-submit → SLURM → NFS → vcell-rest → client +- Verify progress reporting works (client sees iteration updates) +- Verify stop works (SLURM job is cancelled, client gets last progress) +- Monitor for JDBC leak warnings (should be gone) + +#### Commit 8: Remove legacy optimization code (deferred until new path validated) - Delete `OptimizationRunServerResource.java` from `vcell-api` - Delete `OptimizationRunResource.java` (interface) from `vcell-api` - Delete `OptMessage.java` from `vcell-core` @@ -300,25 +382,49 @@ This test exercises the complete production path: REST → DB → ActiveMQ → S - Remove socket initialization from `HtcSimulationWorker.init()` - Remove `submitOptimization()` / `getOptRunJson()` from `VCellApiClient` - Remove optimization route registration from `VCellApiApplication.java` -- Refactor remaining `OptimizationBatchServer` methods into a focused `OptimizationJobSubmitter` class - -### Commit 9: Kubernetes configuration - -- Remove port 8877 from vcell-submit Service -- Verify NFS mount paths for vcell-rest pod include `parest_data` directory -- Update ingress if needed for new `/api/v1/optimization` route +- Remove port 8877 exposure from vcell-submit + +#### Level 3 integration test (future) +- Full end-to-end test with SLURM (`@Tag("SLURM_IT")`) — requires NFS and SSH access to SLURM cluster +- Not run in CI; skipped if required system properties are missing +- Submits a small, fast-converging OptProblem with low max iterations +- Verifies real progress reports and optimized parameter values + +#### Future improvements +- Consider migrating vcell-submit from ActiveMQ 5.x OpenWire client to Artemis JMS client (`jakarta.jms`) for protocol consistency +- Add dead letter and expiry address configuration for opt-request/opt-status queues in Artemis +- Add monitoring/alerting for optimization job failures +- Consider increasing the 200-second client timeout or making it configurable + +## Key files + +| File | Purpose | +|------|---------| +| `vcell-rest/.../handlers/OptimizationResource.java` | REST endpoints | +| `vcell-rest/.../services/OptimizationRestService.java` | DB CRUD and filesystem reads | +| `vcell-rest/.../activemq/OptimizationMQ.java` | AMQP 1.0 producer/consumer | +| `vcell-rest/src/main/resources/application.properties` | AMQP channel config | +| `vcell-server/.../batch/opt/OptimizationBatchServer.java` | OpenWire JMS listener, SLURM dispatch | +| `vcell-server/.../batch/sim/HtcSimulationWorker.java` | Starts opt queue listener in `init()` | +| `vcell-core/.../optimization/OptRequestMessage.java` | Request message type | +| `vcell-core/.../optimization/OptStatusMessage.java` | Status message type | +| `vcell-core/.../optimization/OptJobStatus.java` | Status enum | +| `vcell-core/.../modeldb/OptJobTable.java` | Database table definition | +| `vcell-client/.../copasi/CopasiOptimizationSolverRemote.java` | Desktop client solver | +| `docker/build/Dockerfile-submit-dev` | vcell-submit container with Artemis config | +| `tools/openapi-clients.sh` | OpenAPI client generation script | ## Decommissioning the legacy `/api/v0/optimization` endpoints The legacy endpoints must remain available during a transition period because deployed desktop clients (already installed on user machines) will continue to call `/api/v0/optimization` until they update. The decommissioning plan: -### Phase 1: Parallel operation (commits 1–6) +### Phase 1: Parallel operation (CURRENT) -Both old and new endpoints are live. The new `/api/v1/optimization` endpoints are deployed and functional. The old `/api/v0/optimization` endpoints continue to work as before (same socket-based implementation, same bugs). No client changes yet. +Both old and new endpoints are live. The new `/api/v1/optimization` endpoints are deployed and being validated on dev. The old `/api/v0/optimization` endpoints continue to work as before (same socket-based implementation, same bugs). The desktop client on the `parest-bug` branch uses the new API. -### Phase 2: Client migration (commit 7) +### Phase 2: Client migration (after dev validation) -The desktop client is updated to call `/api/v1/optimization`. New client builds use the new API exclusively. Old client installs still use `/api/v0/`. +Merge `parest-bug` to `master` and release. New client builds use the new API exclusively. Old client installs still use `/api/v0/`. VCell uses a managed client update mechanism — when users launch the desktop client, it checks for updates and prompts to download the latest version. This means the transition window depends on how quickly users update. @@ -333,5 +439,5 @@ After the updated client is released: Once legacy endpoint usage drops to zero (or an acceptable threshold): - Remove the legacy optimization endpoints, socket server, and related code -- Remove port 8877 from the vcell-submit Kubernetes service (commit 9) +- Remove port 8877 from the vcell-submit Kubernetes service - The `VCellApiClient` class itself is not deleted (it may still be used for other legacy endpoints), but its optimization methods are removed From b7ac2e50452afd9d1af437d934d20859f58c253b Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Sat, 11 Apr 2026 12:15:39 -0400 Subject: [PATCH 30/40] Fix COPASI progress buffering and remove broken mkdir in SlurmProxy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Set confirm_overwrite=False in basico.assign_report() so COPASI flushes progress lines incrementally during execution. The default (True) caused COPASI to buffer the entire report until task completion, preventing real-time progress updates in the client. - Remove redundant mkdir on external NFS path in SlurmProxy.createOptJobScript() — vcell-rest already creates the parest_data directory, and the external path is not accessible from inside the vcell-submit container. - Add test_incremental_report_writing using multiprocessing to verify COPASI writes progress to the report file during execution (not just at the end). - Add debug/info logging to CopasiOptimizationSolverRemote polling loop. - Add .gitignore entries for vcell-opt .venv and test artifacts. Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 4 ++ .../vcell-opt/tests/vcellopt_test.py | 65 ++++++++++++++++++- .../vcell-opt/vcell_opt/optService.py | 3 +- .../CopasiOptimizationSolverRemote.java | 9 ++- .../message/server/htc/slurm/SlurmProxy.java | 5 +- 5 files changed, 80 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 882ca3839a..b0ba9fb145 100644 --- a/.gitignore +++ b/.gitignore @@ -249,3 +249,7 @@ pythonData/test_data/SimID_946368938_0_.vcg pythonData/test_data/SimID_946368938_mathmodel.vcml pythonData/test_data/zarr/ + +pythonCopasiOpt/vcell-opt/.venv/ + +pythonCopasiOpt/vcell-opt/test_data/optproblem.report diff --git a/pythonCopasiOpt/vcell-opt/tests/vcellopt_test.py b/pythonCopasiOpt/vcell-opt/tests/vcellopt_test.py index e76aca9c03..5d5644ea8d 100644 --- a/pythonCopasiOpt/vcell-opt/tests/vcellopt_test.py +++ b/pythonCopasiOpt/vcell-opt/tests/vcellopt_test.py @@ -1,6 +1,8 @@ import json +import multiprocessing import os import tempfile +import time from pathlib import Path from typing import List @@ -55,7 +57,8 @@ def test_run() -> None: param_names: List[str] = [param_desc.name for param_desc in vcell_opt_problem.parameter_description_list] f_report_file.write(json.dumps(param_names)+"\n") - basico.assign_report("parest report", task=basico.T.PARAMETER_ESTIMATION, filename=str(report_file), append=True) + basico.assign_report("parest report", task=basico.T.PARAMETER_ESTIMATION, filename=str(report_file), append=True, + confirm_overwrite=False) fit_items = get_fit_parameters(vcell_opt_problem) basico.set_fit_parameters(fit_items) @@ -201,3 +204,63 @@ def test_solver() -> None: if result_file.exists(): os.remove(result_file) + + +def _run_solver_in_subprocess(opt_file: str, report_file: str, result_file: str) -> None: + """Target function for multiprocessing — runs the solver in a separate process.""" + optService.run_command(opt_file=Path(opt_file), report_file=Path(report_file), result_file=Path(result_file)) + + +def test_incremental_report_writing() -> None: + """Verify that the report file is written incrementally during optimization, + not buffered until completion. This requires confirm_overwrite=False in + basico.assign_report() (the default True causes COPASI to buffer all output). + + Uses multiprocessing so the solver runs in a separate process (with its own GIL), + allowing the main process to monitor the report file size during execution. + """ + test_data_dir = Path(__file__).resolve().parent.parent / "test_data" + opt_file = test_data_dir / "optproblem.json" + report_file = test_data_dir / "incremental_test.report" + result_file = test_data_dir / "incremental_test.json" + + if report_file.exists(): + os.remove(report_file) + if result_file.exists(): + os.remove(result_file) + + # Run the solver in a separate process + proc = multiprocessing.Process( + target=_run_solver_in_subprocess, + args=(str(opt_file), str(report_file), str(result_file)) + ) + proc.start() + + # Monitor the report file size from the main process + file_sizes: List[int] = [] + while proc.is_alive(): + if report_file.exists(): + file_sizes.append(report_file.stat().st_size) + time.sleep(0.01) # 10ms polling + + proc.join(timeout=30) + assert proc.exitcode == 0, f"Solver process failed with exit code {proc.exitcode}" + + unique_sizes = sorted(set(file_sizes)) + print(f"Sampled {len(file_sizes)} times, unique file sizes: {unique_sizes}") + + # With confirm_overwrite=False, COPASI flushes progress incrementally. + # We expect at least 3 distinct sizes: header-only, intermediate progress, final. + # With confirm_overwrite=True (old behavior), we'd see only the header size + # until the solver finishes, then a single jump to the final size (2 unique sizes). + assert len(unique_sizes) >= 3, ( + f"Expected at least 3 distinct file sizes (header, intermediate, final) " + f"but got {len(unique_sizes)}: {unique_sizes}. " + f"COPASI may be buffering output — check confirm_overwrite setting." + ) + + # Cleanup + if report_file.exists(): + os.remove(report_file) + if result_file.exists(): + os.remove(result_file) diff --git a/pythonCopasiOpt/vcell-opt/vcell_opt/optService.py b/pythonCopasiOpt/vcell-opt/vcell_opt/optService.py index 0caabc7bb7..a254da56be 100644 --- a/pythonCopasiOpt/vcell-opt/vcell_opt/optService.py +++ b/pythonCopasiOpt/vcell-opt/vcell_opt/optService.py @@ -57,7 +57,8 @@ def run_command(opt_file: Path = typer.Argument(..., file_okay=True, dir_okay=Fa param_names: List[str] = [param_desc.name for param_desc in opt_problem.parameter_description_list] f_report_file.write(json.dumps(param_names)+"\n") - basico.assign_report("parest report", task=basico.T.PARAMETER_ESTIMATION, filename=str(report_file), append=True) + basico.assign_report("parest report", task=basico.T.PARAMETER_ESTIMATION, filename=str(report_file), append=True, + confirm_overwrite=False) fit_items = get_fit_parameters(opt_problem) basico.set_fit_parameters(fit_items) diff --git a/vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java b/vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java index 45b2650699..72fddaf88c 100644 --- a/vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java +++ b/vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java @@ -115,6 +115,11 @@ public static OptimizationResultSet solveRemoteApi( throw UserCancelException.CANCEL_GENERIC; } + lg.debug("job {}: poll status={}, hasProgressReport={}, hasResults={}", + jobId, status.getStatus(), + status.getProgressReport() != null, + status.getResults() != null); + switch (status.getStatus()) { case SUBMITTED: case QUEUED: @@ -147,14 +152,16 @@ public static OptimizationResultSet solveRemoteApi( if (status.getResults() != null) { String resultsJson = objectMapper.writeValueAsString(status.getResults()); optRun = objectMapper.readValue(resultsJson, Vcellopt.class); + lg.info("job {}: COMPLETE, optResultSet={}", jobId, optRun.getOptResultSet() != null); if (optRun.getOptResultSet() != null && optRun.getOptResultSet().getOptProgressReport() != null) { final OptProgressReport finalProgress = optRun.getOptResultSet().getOptProgressReport(); progressDispatcher.accept(() -> optSolverCallbacks.setProgressReport(finalProgress)); } - lg.info("job {}: COMPLETE {}", jobId, optRun.getOptResultSet()); if (clientTaskStatusSupport != null) { clientTaskStatusSupport.setProgress(100); } + } else { + lg.warn("job {}: COMPLETE but results are null", jobId); } break; diff --git a/vcell-server/src/main/java/cbit/vcell/message/server/htc/slurm/SlurmProxy.java b/vcell-server/src/main/java/cbit/vcell/message/server/htc/slurm/SlurmProxy.java index a102dc8bc4..98f1686b87 100644 --- a/vcell-server/src/main/java/cbit/vcell/message/server/htc/slurm/SlurmProxy.java +++ b/vcell-server/src/main/java/cbit/vcell/message/server/htc/slurm/SlurmProxy.java @@ -1119,11 +1119,10 @@ String createOptJobScript(String jobName, File optProblemInputFile, File optProb LineStringBuilder lsb = new LineStringBuilder(); slurmScriptInit(jobName, false, memoryMBAllowed, lsb); + // vcell-rest creates the parest_data directory before writing the input file. + // Compute the external path for the SLURM script bindings (host-visible NFS mount point). File optDataDir = optProblemInputFile.getParentFile(); File optDataDirExternal = new File(optDataDir.getAbsolutePath().replace(primaryDataDirInternal, primaryDataDirExternal)); - if (!optDataDirExternal.exists() && !optDataDirExternal.mkdir()){ - LG.error("failed to make optimization data directory "+optDataDir.getAbsolutePath()); - } List bindings = List.of( new SingularityBinding(optDataDirExternal.getAbsolutePath(), "/simdata"), From fd99ebbc6da919564dd2b43d6eae05d88fdabc9e Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Sat, 11 Apr 2026 12:15:44 -0400 Subject: [PATCH 31/40] Add requirements section and post-migration notes to design doc Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/parameter-estimation-service.md | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/docs/parameter-estimation-service.md b/docs/parameter-estimation-service.md index 9240bca3c6..b619a57914 100644 --- a/docs/parameter-estimation-service.md +++ b/docs/parameter-estimation-service.md @@ -1,5 +1,38 @@ # Parameter Estimation Service Migration +## Requirements + +Parameter estimation allows VCell users to fit model parameters to experimental data. The service must satisfy the following requirements: + +### Functional requirements + +1. **Submit optimization job.** An authenticated user submits an `OptProblem` (SBML model, experimental data, parameter bounds, optimization method) and receives a job ID. The problem is dispatched to a SLURM cluster for execution using the COPASI optimization engine. + +2. **Real-time progress reporting.** While the solver runs, the client receives periodic updates showing: + - Objective function value vs. function evaluations (displayed as a graph) + - Current best parameter values (displayed in a table) + - Progress updates every ~2 seconds via client polling + +3. **Retrieve results.** When optimization completes, the client retrieves the final optimized parameter values, objective function value, and the full progress history. + +4. **Stop running job.** A user can stop an optimization mid-run. The best parameters found up to the stop point are returned to the client, allowing the user to accept partial results when convergence is sufficient. + +5. **Job ownership.** Only the user who submitted a job can query its status, retrieve results, or stop it. + +6. **List jobs.** A user can list their optimization jobs with current status. + +### Non-functional requirements + +7. **Survive pod restarts.** Job state must be persisted so that in-flight jobs are not lost when the REST service or submit service restarts. This is routine in Kubernetes. + +8. **No single point of failure for job dispatch.** Communication between the REST service and the batch submission service must use a durable message broker, not in-memory connections. + +9. **Collision-free job IDs.** Job identifiers must be unique, using database-sequence keys rather than random numbers. + +10. **Auto-generated API clients.** The REST API must have an OpenAPI spec, and Java/Python/TypeScript clients must be auto-generated from it to stay in sync with the server. + +11. **Shared filesystem for solver I/O.** The optimization problem input, progress report, and result output are files on a shared NFS mount — matching the Python solver's file-based interface. The database tracks metadata and status; the filesystem holds the data. + ## Motivation Parameter estimation (curve fitting) is a core VCell capability that allows users to optimize model parameters against experimental data using the COPASI optimization engine. The current implementation has been unreliable in production (GitHub #1653), and the root cause is architectural: the REST-to-batch-server communication relies on persistent in-memory TCP socket connections that are inherently fragile. @@ -384,6 +417,12 @@ All commits listed below are on the `parest-bug` branch, ahead of `master`. - Remove optimization route registration from `VCellApiApplication.java` - Remove port 8877 exposure from vcell-submit +#### Update design documentation after legacy removal +- After commit 8, remove the "Motivation" section's references to the legacy design (socket protocol, random IDs, etc.) — they will no longer be relevant context +- Remove the "Decommissioning" section (will be complete) +- Update architecture diagram to remove references to legacy `/api/v0/` endpoints +- Simplify the document to be a pure design reference rather than a migration plan + #### Level 3 integration test (future) - Full end-to-end test with SLURM (`@Tag("SLURM_IT")`) — requires NFS and SSH access to SLURM cluster - Not run in CI; skipped if required system properties are missing From 629888a7fe3d69f3e6df235c8a3d6f04a10ca10c Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Sat, 11 Apr 2026 12:51:12 -0400 Subject: [PATCH 32/40] Upgrade COPASI/basico and modernize vcell-opt Docker image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Upgrade copasi-basico 0.40 → 0.86, python-copasi 4.37.264 → 4.46.300 - Set minimum Python to 3.10 (matches Dockerfile and COPASI wheel availability) - Fix report format: use separator='\t' parameter instead of inline '\\\t' body items, which new basico writes as literal backslashes - Upgrade Dockerfile base from bullseye (EOL) to bookworm (Debian 12) - Add gcc and python3-dev to Dockerfile for psutil compilation - Fix deprecated poetry.dev-dependencies → poetry.group.dev.dependencies - Regenerate poetry.lock Co-Authored-By: Claude Opus 4.6 (1M context) --- pythonCopasiOpt/Dockerfile | 5 +- pythonCopasiOpt/vcell-opt/poetry.lock | 496 ++++++++++++++---- pythonCopasiOpt/vcell-opt/pyproject.toml | 8 +- .../vcell-opt/tests/vcellopt_test.py | 3 +- .../vcell-opt/vcell_opt/optService.py | 3 +- 5 files changed, 417 insertions(+), 98 deletions(-) diff --git a/pythonCopasiOpt/Dockerfile b/pythonCopasiOpt/Dockerfile index 78e5752f21..32dd8ed8a7 100644 --- a/pythonCopasiOpt/Dockerfile +++ b/pythonCopasiOpt/Dockerfile @@ -1,7 +1,8 @@ -FROM python:3.10-slim-bullseye +FROM python:3.10-slim-bookworm RUN apt -y update && apt -y upgrade && \ - apt install -y curl + apt install -y curl gcc python3-dev && \ + rm -rf /var/lib/apt/lists/* RUN mkdir -p /usr/local/app/vcell/installDir && \ mkdir -p /usr/local/app/vcell/installDir/python/vcell_opt diff --git a/pythonCopasiOpt/vcell-opt/poetry.lock b/pythonCopasiOpt/vcell-opt/poetry.lock index 557c4ee14f..c82400c594 100644 --- a/pythonCopasiOpt/vcell-opt/poetry.lock +++ b/pythonCopasiOpt/vcell-opt/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. [[package]] name = "anyio" @@ -6,6 +6,7 @@ version = "3.6.2" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.6.2" +groups = ["main"] files = [ {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, @@ -17,7 +18,7 @@ sniffio = ">=1.1" [package.extras] doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] +test = ["contextlib2 ; python_version < \"3.7\"", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4) ; python_version < \"3.8\"", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15) ; python_version < \"3.7\" and platform_python_implementation == \"CPython\" and platform_system != \"Windows\"", "uvloop (>=0.15) ; python_version >= \"3.7\" and platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] trio = ["trio (>=0.16,<0.22)"] [[package]] @@ -26,6 +27,8 @@ version = "0.1.3" description = "Disable App Nap on macOS >= 10.9" optional = false python-versions = "*" +groups = ["main"] +markers = "platform_system == \"Darwin\" or sys_platform == \"darwin\"" files = [ {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, @@ -37,6 +40,7 @@ version = "21.3.0" description = "The secure Argon2 password hashing algorithm." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "argon2-cffi-21.3.0.tar.gz", hash = "sha256:d384164d944190a7dd7ef22c6aa3ff197da12962bd04b17f64d4e93d934dba5b"}, {file = "argon2_cffi-21.3.0-py3-none-any.whl", hash = "sha256:8c976986f2c5c0e5000919e6de187906cfd81fb1c72bf9d88c01177e77da7f80"}, @@ -56,6 +60,7 @@ version = "21.2.0" description = "Low-level CFFI bindings for Argon2" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"}, @@ -93,6 +98,7 @@ version = "1.3.0" description = "Better dates & times for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80"}, {file = "arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85"}, @@ -112,6 +118,7 @@ version = "2.2.1" description = "Annotate AST trees with source code positions" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "asttokens-2.2.1-py2.py3-none-any.whl", hash = "sha256:6b0ac9e93fb0335014d382b8fa9b3afa7df546984258005da0b9e7095b3deb1c"}, {file = "asttokens-2.2.1.tar.gz", hash = "sha256:4622110b2a6f30b77e1473affaa97e711bc2f07d3f10848420ff1898edbe94f3"}, @@ -129,16 +136,17 @@ version = "22.1.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.5" +groups = ["main", "dev"] files = [ {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, ] [package.extras] -dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] -tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] -tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] +tests-no-zope = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] [[package]] name = "backcall" @@ -146,6 +154,7 @@ version = "0.2.0" description = "Specifications for callback functions passed in to an API" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, @@ -157,6 +166,7 @@ version = "4.11.1" description = "Screen-scraping library" optional = false python-versions = ">=3.6.0" +groups = ["main"] files = [ {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, @@ -175,6 +185,7 @@ version = "5.0.1" description = "An easy safelist-based HTML-sanitizing tool." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "bleach-5.0.1-py3-none-any.whl", hash = "sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a"}, {file = "bleach-5.0.1.tar.gz", hash = "sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c"}, @@ -186,7 +197,7 @@ webencodings = "*" [package.extras] css = ["tinycss2 (>=1.1.0,<1.2)"] -dev = ["Sphinx (==4.3.2)", "black (==22.3.0)", "build (==0.8.0)", "flake8 (==4.0.1)", "hashin (==0.17.0)", "mypy (==0.961)", "pip-tools (==6.6.2)", "pytest (==7.1.2)", "tox (==3.25.0)", "twine (==4.0.1)", "wheel (==0.37.1)"] +dev = ["Sphinx (==4.3.2)", "black (==22.3.0) ; implementation_name == \"cpython\"", "build (==0.8.0)", "flake8 (==4.0.1)", "hashin (==0.17.0)", "mypy (==0.961) ; implementation_name == \"cpython\"", "pip-tools (==6.6.2)", "pytest (==7.1.2)", "tox (==3.25.0)", "twine (==4.0.1)", "wheel (==0.37.1)"] [[package]] name = "cffi" @@ -194,6 +205,7 @@ version = "1.15.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, @@ -270,6 +282,7 @@ version = "8.1.3" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, @@ -284,10 +297,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {dev = "sys_platform == \"win32\""} [[package]] name = "commonmark" @@ -295,6 +310,7 @@ version = "0.9.1" description = "Python parser for the CommonMark Markdown spec" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, @@ -309,6 +325,7 @@ version = "1.0.6" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "contourpy-1.0.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:613c665529899b5d9fade7e5d1760111a0b011231277a0d36c49f0d3d6914bd6"}, {file = "contourpy-1.0.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:78ced51807ccb2f45d4ea73aca339756d75d021069604c2fccd05390dc3c28eb"}, @@ -393,24 +410,30 @@ test-no-codebase = ["Pillow", "matplotlib", "pytest"] [[package]] name = "copasi-basico" -version = "0.40" +version = "0.86" description = "Simplified COPASI interface for python" optional = false python-versions = "*" +groups = ["main"] files = [ - {file = "copasi_basico-0.40-py3-none-any.whl", hash = "sha256:1a67220a1bb898b8eee29ac30b1c2498145c01036174b5dbc9bfc11b186a5292"}, - {file = "copasi_basico-0.40.tar.gz", hash = "sha256:72d836a461875cd4a83c4b15146b24d0bf83f0530ea67d413b32cdae2f1bc09e"}, + {file = "copasi_basico-0.86-py3-none-any.whl", hash = "sha256:d92b93ad28edcd84b83962913d1fc973906ca612c01e954007d23a9031167058"}, + {file = "copasi_basico-0.86.tar.gz", hash = "sha256:de3090ee3bb46a64953ab35b759023d20081a29d518eb3a5d0377fe23765f336"}, ] [package.dependencies] +lxml = "*" matplotlib = "*" numpy = "*" pandas = "*" python-copasi = "*" PyYAML = "*" +scipy = "*" [package.extras] +docs = ["sphinx", "sphinx_rtd_theme", "sphinxcontrib", "sphinxcontrib-bibtex", "sphinxcontrib-mermaid", "sphinxcontrib-plantuml", "sphinxcontrib-programoutput"] petab = ["copasi-petab-importer", "petab", "petab-select"] +ssr = ["libssr", "python-libsbml"] +tqdm = ["tqdm"] [[package]] name = "cycler" @@ -418,6 +441,7 @@ version = "0.11.0" description = "Composable style cycles" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"}, {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"}, @@ -429,6 +453,7 @@ version = "1.6.4" description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "debugpy-1.6.4-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:6ae238943482c78867ac707c09122688efb700372b617ffd364261e5e41f7a2f"}, {file = "debugpy-1.6.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a39e7da178e1f22f4bc04b57f085e785ed1bcf424aaf318835a1a7129eefe35"}, @@ -456,6 +481,7 @@ version = "5.1.1" description = "Decorators for Humans" optional = false python-versions = ">=3.5" +groups = ["main"] files = [ {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, @@ -467,6 +493,7 @@ version = "0.7.1" description = "XML bomb protection for Python stdlib modules" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] files = [ {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, @@ -478,6 +505,7 @@ version = "0.4" description = "Discover and load entry points from installed packages." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "entrypoints-0.4-py3-none-any.whl", hash = "sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f"}, {file = "entrypoints-0.4.tar.gz", hash = "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4"}, @@ -489,6 +517,8 @@ version = "1.0.4" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"}, {file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"}, @@ -503,13 +533,14 @@ version = "1.2.0" description = "Get the currently executing AST node of a frame, and other information" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "executing-1.2.0-py2.py3-none-any.whl", hash = "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc"}, {file = "executing-1.2.0.tar.gz", hash = "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107"}, ] [package.extras] -tests = ["asttokens", "littleutils", "pytest", "rich"] +tests = ["asttokens", "littleutils", "pytest", "rich ; python_version >= \"3.11\""] [[package]] name = "fastjsonschema" @@ -517,6 +548,7 @@ version = "2.16.2" description = "Fastest Python implementation of JSON schema" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "fastjsonschema-2.16.2-py3-none-any.whl", hash = "sha256:21f918e8d9a1a4ba9c22e09574ba72267a6762d47822db9add95f6454e51cc1c"}, {file = "fastjsonschema-2.16.2.tar.gz", hash = "sha256:01e366f25d9047816fe3d288cbfc3e10541daf0af2044763f3d0ade42476da18"}, @@ -531,24 +563,25 @@ version = "4.38.0" description = "Tools to manipulate font files" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "fonttools-4.38.0-py3-none-any.whl", hash = "sha256:820466f43c8be8c3009aef8b87e785014133508f0de64ec469e4efb643ae54fb"}, {file = "fonttools-4.38.0.zip", hash = "sha256:2bb244009f9bf3fa100fc3ead6aeb99febe5985fa20afbfbaa2f8946c2fbdaf1"}, ] [package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=14.0.0)", "xattr", "zopfli (>=0.1.4)"] +all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=14.0.0) ; python_version < \"3.11\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"] graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "scipy"] +interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "scipy ; platform_python_implementation != \"PyPy\""] lxml = ["lxml (>=4.0,<5)"] pathops = ["skia-pathops (>=0.5.0)"] plot = ["matplotlib"] repacker = ["uharfbuzz (>=0.23.0)"] symfont = ["sympy"] -type1 = ["xattr"] +type1 = ["xattr ; sys_platform == \"darwin\""] ufo = ["fs (>=2.2.0,<3)"] -unicode = ["unicodedata2 (>=14.0.0)"] -woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] +unicode = ["unicodedata2 (>=14.0.0) ; python_version < \"3.11\""] +woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"] [[package]] name = "fqdn" @@ -556,6 +589,7 @@ version = "1.5.1" description = "Validates fully-qualified domain names against RFC 1123, so that they are acceptable to modern bowsers" optional = false python-versions = ">=2.7, !=3.0, !=3.1, !=3.2, !=3.3, !=3.4, <4" +groups = ["main"] files = [ {file = "fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014"}, {file = "fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f"}, @@ -567,36 +601,19 @@ version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" +groups = ["main"] files = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] -[[package]] -name = "importlib-metadata" -version = "5.1.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.7" -files = [ - {file = "importlib_metadata-5.1.0-py3-none-any.whl", hash = "sha256:d84d17e21670ec07990e1044a99efe8d615d860fd176fc29ef5c306068fda313"}, - {file = "importlib_metadata-5.1.0.tar.gz", hash = "sha256:d5059f9f1e8e41f80e9c56c2ee58811450c31984dfa625329ffd7c0dad88a73b"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -perf = ["ipython"] -testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] - [[package]] name = "iniconfig" version = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, @@ -608,6 +625,7 @@ version = "6.17.1" description = "IPython Kernel for Jupyter" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "ipykernel-6.17.1-py3-none-any.whl", hash = "sha256:3a9a1b2ad6dbbd5879855aabb4557f08e63fa2208bffed897f03070e2bb436f6"}, {file = "ipykernel-6.17.1.tar.gz", hash = "sha256:e178c1788399f93a459c241fe07c3b810771c607b1fb064a99d2c5d40c90c5d4"}, @@ -636,6 +654,7 @@ version = "8.10.0" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "ipython-8.10.0-py3-none-any.whl", hash = "sha256:b38c31e8fc7eff642fc7c597061fff462537cf2314e3225a19c906b7b0d8a345"}, {file = "ipython-8.10.0.tar.gz", hash = "sha256:b13a1d6c1f5818bd388db53b7107d17454129a70de2b87481d555daede5eb49e"}, @@ -674,6 +693,7 @@ version = "0.2.0" description = "Vestigial utilities from IPython" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, @@ -685,6 +705,7 @@ version = "8.0.2" description = "Jupyter interactive widgets" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "ipywidgets-8.0.2-py3-none-any.whl", hash = "sha256:1dc3dd4ee19ded045ea7c86eb273033d238d8e43f9e7872c52d092683f263891"}, {file = "ipywidgets-8.0.2.tar.gz", hash = "sha256:08cb75c6e0a96836147cbfdc55580ae04d13e05d26ffbc377b4e1c68baa28b1f"}, @@ -706,6 +727,7 @@ version = "20.11.0" description = "Operations with ISO 8601 durations" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042"}, {file = "isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9"}, @@ -720,6 +742,7 @@ version = "0.18.2" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "jedi-0.18.2-py2.py3-none-any.whl", hash = "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e"}, {file = "jedi-0.18.2.tar.gz", hash = "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612"}, @@ -739,6 +762,7 @@ version = "3.1.2" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, @@ -756,6 +780,7 @@ version = "2.4" description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +groups = ["main"] files = [ {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"}, {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"}, @@ -767,6 +792,7 @@ version = "4.17.3" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "jsonschema-4.17.3-py3-none-any.whl", hash = "sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6"}, {file = "jsonschema-4.17.3.tar.gz", hash = "sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d"}, @@ -794,6 +820,7 @@ version = "1.0.0" description = "Jupyter metapackage. Install all the Jupyter components in one go." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "jupyter-1.0.0-py2.py3-none-any.whl", hash = "sha256:5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78"}, {file = "jupyter-1.0.0.tar.gz", hash = "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f"}, @@ -814,6 +841,7 @@ version = "7.4.8" description = "Jupyter protocol implementation and client libraries" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "jupyter_client-7.4.8-py3-none-any.whl", hash = "sha256:d4a67ae86ee014bcb96bd8190714f6af921f2b0f52f4208b086aa5acfd9f8d65"}, {file = "jupyter_client-7.4.8.tar.gz", hash = "sha256:109a3c33b62a9cf65aa8325850a0999a795fac155d9de4f7555aef5f310ee35a"}, @@ -838,6 +866,7 @@ version = "6.4.4" description = "Jupyter terminal console" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "jupyter_console-6.4.4-py3-none-any.whl", hash = "sha256:756df7f4f60c986e7bc0172e4493d3830a7e6e75c08750bbe59c0a5403ad6dee"}, {file = "jupyter_console-6.4.4.tar.gz", hash = "sha256:172f5335e31d600df61613a97b7f0352f2c8250bbd1092ef2d658f77249f89fb"}, @@ -851,7 +880,7 @@ prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" pygments = "*" [package.extras] -test = ["pexpect"] +test = ["pexpect ; sys_platform != \"win32\""] [[package]] name = "jupyter-core" @@ -859,6 +888,7 @@ version = "5.1.0" description = "Jupyter core package. A base package on which Jupyter projects rely." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "jupyter_core-5.1.0-py3-none-any.whl", hash = "sha256:f5740d99606958544396914b08e67b668f45e7eff99ab47a7f4bcead419c02f4"}, {file = "jupyter_core-5.1.0.tar.gz", hash = "sha256:a5ae7c09c55c0b26f692ec69323ba2b62e8d7295354d20f6cd57b749de4a05bf"}, @@ -879,6 +909,7 @@ version = "0.6.3" description = "Jupyter Event System library" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "jupyter_events-0.6.3-py3-none-any.whl", hash = "sha256:57a2749f87ba387cd1bfd9b22a0875b889237dbf2edc2121ebb22bde47036c17"}, {file = "jupyter_events-0.6.3.tar.gz", hash = "sha256:9a6e9995f75d1b7146b436ea24d696ce3a35bfa8bfe45e0c33c334c79464d0b3"}, @@ -903,6 +934,7 @@ version = "2.7.2" description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "jupyter_server-2.7.2-py3-none-any.whl", hash = "sha256:98a375347b580e837e7016007c24680a4261ed8ad7cd35196ac087d229f48e5a"}, {file = "jupyter_server-2.7.2.tar.gz", hash = "sha256:d64fb4e593907290e5df916e3c9399c15ab2cd7bdb71cbcd1d36452dbfb30523"}, @@ -939,6 +971,7 @@ version = "0.4.4" description = "A Jupyter Server Extension Providing Terminals." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "jupyter_server_terminals-0.4.4-py3-none-any.whl", hash = "sha256:75779164661cec02a8758a5311e18bb8eb70c4e86c6b699403100f1585a12a36"}, {file = "jupyter_server_terminals-0.4.4.tar.gz", hash = "sha256:57ab779797c25a7ba68e97bcfb5d7740f2b5e8a83b5e8102b10438041a7eac5d"}, @@ -958,6 +991,7 @@ version = "0.2.2" description = "Pygments theme using JupyterLab CSS variables" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "jupyterlab_pygments-0.2.2-py2.py3-none-any.whl", hash = "sha256:2405800db07c9f770863bcf8049a529c3dd4d3e28536638bd7c1c01d2748309f"}, {file = "jupyterlab_pygments-0.2.2.tar.gz", hash = "sha256:7405d7fde60819d905a9fa8ce89e4cd830e318cdad22a0030f7a901da705585d"}, @@ -969,6 +1003,7 @@ version = "3.0.3" description = "Jupyter interactive widgets for JupyterLab" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "jupyterlab_widgets-3.0.3-py3-none-any.whl", hash = "sha256:6aa1bc0045470d54d76b9c0b7609a8f8f0087573bae25700a370c11f82cb38c8"}, {file = "jupyterlab_widgets-3.0.3.tar.gz", hash = "sha256:c767181399b4ca8b647befe2d913b1260f51bf9d8ef9b7a14632d4c1a7b536bd"}, @@ -980,6 +1015,7 @@ version = "1.4.4" description = "A fast implementation of the Cassowary constraint solver" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6"}, {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c"}, @@ -1051,12 +1087,163 @@ files = [ {file = "kiwisolver-1.4.4.tar.gz", hash = "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955"}, ] +[[package]] +name = "lxml" +version = "6.0.3" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "lxml-6.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c524cf8c3b8d71dfc3de6cfb225138a876862a92d88bfa22eb9ff020729d45"}, + {file = "lxml-6.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a10f9967859229cae38b1aa7a96eb655c96b8adc96989b52c5b1f77d963a77a4"}, + {file = "lxml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:edccf1157677db1da741d042601754b94af3926310c5763179200718ca738e70"}, + {file = "lxml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:20f8caa9beb61a688c4428631cb47fd6e0ba75ef30091cec5fee992138b2be77"}, + {file = "lxml-6.0.3-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0c88ca5fb307f7e817fc427681126e4712d3452258577bcb4ca86594c934852"}, + {file = "lxml-6.0.3-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:72d108ef9e39f45c6865ea8a5ba6736c0b1a33ddd6343653775349869e58c30b"}, + {file = "lxml-6.0.3-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:b044fe3bdb8b68efa33cb5917ae9379f07ec2e416ecd18cf5d333650d6d2fcbb"}, + {file = "lxml-6.0.3-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:a4dc9f81707b9b56888fa7d3a889ac5219724cf0fbecab90ea5b197faf649534"}, + {file = "lxml-6.0.3-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:59ff244cee0270fc4cf5f2ee920d4493ee88d0bcbc6e8465e9ef01439f1785e7"}, + {file = "lxml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2a49123cc3209ccad7c4c5a4133bcfcfd4875f30461ea4d0aaa84e6608f712c5"}, + {file = "lxml-6.0.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:26cdd7c3f4c3b932b28859d0b70966c2ec8b67c106717d6320121002f8f99025"}, + {file = "lxml-6.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6556b3197bd8a237a16fcd7278d09f094c5777ae36a1435b5e8e488826386d96"}, + {file = "lxml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:87bebd6836e88c0a007f66b89814daf5d7041430eb491c91d1851abc89aa6e93"}, + {file = "lxml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:0b012cf200736d90f828b3ab4206857772c61b710f0a98d3814c7994decb6652"}, + {file = "lxml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:794b42f0112adfa3f23670aba5bc0ac9c9adfcee699c0df129b0186c812ac3ff"}, + {file = "lxml-6.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:ecdded59dc50c0c28f652a98f69a7ada8bd2377248bf48c4a83c81204eb58b33"}, + {file = "lxml-6.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c8184fdb2259bda1db2db9d6e25f667769afc2531830b4fa29f83f66a7872dea"}, + {file = "lxml-6.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b0f01fb8bdcaf4aa69cf55b2b2f8ef722e4423e1c020e7250dcb89a1d5db38e"}, + {file = "lxml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fab00cef83d4f9d76c5e0722346e84bc174b071d68b4f725aeb0bf3877b9e6a6"}, + {file = "lxml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f753db5785ce019d7b25bb75638ef5a42a0e208aa9f19933262134e668ca6af"}, + {file = "lxml-6.0.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27e317e554bc6086a082688ddf137437e5f7f20ffdd736a6f5b4e3ed1ecf1247"}, + {file = "lxml-6.0.3-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:feb5b9ed7d0510663a78b94f2b417a41c41b42a7bb157ef398ef9d78e6f0fd50"}, + {file = "lxml-6.0.3-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:51014ee2ab2091dcd9cdef92532f0a1addb7c2cc52a2bd70682e441363de5c0d"}, + {file = "lxml-6.0.3-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:abc39c4fb67f029400608f9a3a4a3f351ccb3c061b05fd3ad113e4cfbba8a8ee"}, + {file = "lxml-6.0.3-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:38652c280cf50cc5cf955e3d0b691fa6a97046d84407bbae340d8e353f9014ef"}, + {file = "lxml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c3de55b53f69ffa2fcfd897bd8a7e62f0f88a40a8a0c544e171e813f9d4ddbf5"}, + {file = "lxml-6.0.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bd4f70e091f2df300396bc9ce36963f90b87611324c2ca750072a6e6375beba2"}, + {file = "lxml-6.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:c157bfef4e3b19688eb4da783c5bfabf5a3ac1ac8d317e0906f3feb18d4c89b7"}, + {file = "lxml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8d10a75e4d0a6a9ac2fec2f7ade682f468b51935102c70dab638fa4e94ffcb04"}, + {file = "lxml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:d573b81c29e20b1513afa386a544797a99cecde5497e6c77b6dfa4484112c819"}, + {file = "lxml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:ac63a1ef1899ccadace10ac937c41321672771378374c254e931d001448ae372"}, + {file = "lxml-6.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:10bc4f37c28b4e1b3e901dde66e3a096eb128acf388d5b2962dc2941284293bb"}, + {file = "lxml-6.0.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ad6952810349cbfb843fe15e8afc580b2712359ae42b1d2b05d097bd48c4aea4"}, + {file = "lxml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b81ec1ecac3be8c1ff1e00ca1c1baf8122e87db9000cd2549963847bd5e3b41"}, + {file = "lxml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:448e69211e59c39f398990753d15ba49f7218ec128f64ac8012ef16762e509a3"}, + {file = "lxml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6289cb9145fbbc5b0e159c9fcd7fc09446dadc6b60b72c4d1012e80c7c727970"}, + {file = "lxml-6.0.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b68c29aac4788438b07d768057836de47589c7deaa3ad8dc4af488dfc27be388"}, + {file = "lxml-6.0.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:50293e024afe5e2c25da2be68c8ceca8618912a0701a73f75b488317c8081aa6"}, + {file = "lxml-6.0.3-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac65c08ba1bd90f662cb1d5c79f7ae4c53b1c100f0bb6ec5df1f40ac29028a7e"}, + {file = "lxml-6.0.3-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:16fbcf06ae534b2fa5bcdc19fcf6abd9df2e74fe8563147d1c5a687a130efed4"}, + {file = "lxml-6.0.3-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:3a0484bd1e84f82766befcbd71cccd7307dacfe08071e4dbc1d9a9b498d321e8"}, + {file = "lxml-6.0.3-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c137f8c8419c3de93e2998131d94628805f148e52b34da6d7533454e4d78bc2a"}, + {file = "lxml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:775266571f7027b1d77f5fce18a247b24f51a4404bdc1b90ec56be9b1e3801b9"}, + {file = "lxml-6.0.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:aa18653b795d2c273b8676f7ad2ca916d846d15864e335f746658e4c28eb5168"}, + {file = "lxml-6.0.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:cbffd22fc8e4d80454efa968b0c93440a00b8b8a817ce0c29d2c6cb5ad324362"}, + {file = "lxml-6.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7373ede7ccb89e6f6e39c1423b3a4d4ee48035d3b4619a6addced5c8b48d0ecc"}, + {file = "lxml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e759ff1b244725fef428c6b54f3dab4954c293b2d242a5f2e79db5cc3873de51"}, + {file = "lxml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:f179bae37ad673f57756b59f26833b7922230bef471fdb29492428f152bae8c6"}, + {file = "lxml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:8eeec925ad7f81886d413b3a1f8715551f75543519229a9b35e957771e1826d5"}, + {file = "lxml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:f96bba9a26a064ce9e11099bad12fb08384b64d3acc0acf94bf386ca5cf4f95f"}, + {file = "lxml-6.0.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:83c1d75e9d124ab82a4ddaf59135112f0dc49526b47355e5928ae6126a68e236"}, + {file = "lxml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b683665d0287308adafc90a5617a51a508d8af8c7040693693bb333b5f4474fe"}, + {file = "lxml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ed31e5852cd938704bc6c7a3822cbf84c7fa00ebfa914a1b4e2392d44f45bdfb"}, + {file = "lxml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8922a30704a4421d69a19e0499db5861da686c0bccc3a79cf3946e3155cf25f9"}, + {file = "lxml-6.0.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a1adb0e220cb8691202ba9d97646a06292657a122df4b92733861d42f7cf4d2"}, + {file = "lxml-6.0.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:821fd53699eb498990c915ba955a392d07246454c9405e6c1d0692362503013d"}, + {file = "lxml-6.0.3-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04b7cedf52e125f86d0d426635e7fbe8e353d4cc272a1757888e3c072424381d"}, + {file = "lxml-6.0.3-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:9d98063e6ae0da5084ec46952bb0a5ccb5e2cad168e32b4d65d1ec84e4b4ebd4"}, + {file = "lxml-6.0.3-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:ce01ab3449015358f766a1950b3d818eedf9d4cdec3fa87e4eecaad10c0784db"}, + {file = "lxml-6.0.3-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d38c25bad123d6ce30bb37931d90a4e8a167cd796eeae9cd16c2bfce52718f8e"}, + {file = "lxml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9b8e0779780026979f217603385995202f364adc9807bd21210d81b9f562fc4e"}, + {file = "lxml-6.0.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8c082ad2398664213a4bb5d133e2eb8bf239220b7d6688f8c8ffa9050057501f"}, + {file = "lxml-6.0.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dfc80c74233fe01157ab550fb12b9d07a2f1fa7c5900cefb484e3bf02e856fbc"}, + {file = "lxml-6.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5c45bdcdc2ca6cf26fddff3faa5de7a2ed7c7f6016b3de80125313a37f972378"}, + {file = "lxml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99457524afd384c330dc51e527976653d543ccadfa815d9f2d92c5911626e536"}, + {file = "lxml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:c8e3b8a54e65393ce1d5c7d9753fe756f0d96089e7163b20ddec3e5bb56a963e"}, + {file = "lxml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:724b26a38cef98d6869d00a33cb66083bee967598e44f6a8e53f1dd283c851b0"}, + {file = "lxml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:f27373113fda6621e4201f529908a24c8a190c2af355aed4711dadca44db4673"}, + {file = "lxml-6.0.3-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8c08926678852a233bf1ef645c4d683d56107f814482f8f41b21ef2c7659790e"}, + {file = "lxml-6.0.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2ce76d113a7c3bf42761ec1de7ca615b0cbf9d8ae478eb1d6c20111d9c9fc098"}, + {file = "lxml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83eca62141314d641ebe8089ffa532bbf572ea07dd6255b58c40130d06bb2509"}, + {file = "lxml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d8781d812bb8efd47c35651639da38980383ff0d0c1f3269ade23e3a90799079"}, + {file = "lxml-6.0.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19b079e81aa3a31b523a224b0dd46da4f56e1b1e248eef9a599e5c885c788813"}, + {file = "lxml-6.0.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6c055bafdcb53e7f9f75e22c009cd183dd410475e21c296d599531d7f03d1bf5"}, + {file = "lxml-6.0.3-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f1594a183cee73f9a1dbfd35871c4e04b461f47eeb9bcf80f7d7856b1b136d"}, + {file = "lxml-6.0.3-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:a6380c5035598e4665272ad3fc86c96ddb2a220d4059cce5ba4b660f78346ad9"}, + {file = "lxml-6.0.3-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:143ac903fb6c9be6da613390825c8e8bb8c8d71517d43882031f6b9bc89770ef"}, + {file = "lxml-6.0.3-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4fff7d77f440378cd841e340398edf5dbefee334816efbf521bb6e31651e54e"}, + {file = "lxml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:631567ffc3ddb989ccdcd28f6b9fa5aab1ec7fc0e99fe65572b006a6aad347e2"}, + {file = "lxml-6.0.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:38acf7171535ffa7fff1fcec8b82ebd4e55cd02e581efe776928108421accaa1"}, + {file = "lxml-6.0.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:06b9f3ac459b4565bbaa97aa5512aa7f9a1188c662f0108364f288f6daf35773"}, + {file = "lxml-6.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2773dbe2cedee81f2769bd5d24ceb4037706cf032e1703513dd0e9476cd9375f"}, + {file = "lxml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:30c437d8bb9a9a9edff27e85b694342e47a26a6abc249abe00584a4824f9d80d"}, + {file = "lxml-6.0.3-cp314-cp314-win32.whl", hash = "sha256:1b60a3a1205f869bd47874787c792087174453b1a869db4837bf5b3ff92be017"}, + {file = "lxml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:5b6913a68d98c58c673667c864500ba31bc9b0f462effac98914e9a92ebacd2e"}, + {file = "lxml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:1b36a3c73f2a6d9c2bfae78089ca7aedae5c2ee5fd5214a15f00b2f89e558ba7"}, + {file = "lxml-6.0.3-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:239e9a6be3a79c03ec200d26f7bb17a4414704a208059e20050bf161e2d8848a"}, + {file = "lxml-6.0.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:16e5cbaa1a6351f2abefa4072e9aac1f09103b47fe7ab4496d54e5995b065162"}, + {file = "lxml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:89f8746c206d8cf2c167221831645d6cc2b24464afd9c428a5eb3fd34c584eb1"}, + {file = "lxml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5d559a84b2fd583e5bcf8ec4af1ec895f98811684d5fbd6524ea31a04f92d4ad"}, + {file = "lxml-6.0.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7966fbce2d18fde579d5593933d36ad98cc7c8dc7f2b1916d127057ce0415062"}, + {file = "lxml-6.0.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a1f258e6aa0e6eda2c1199f5582c062c96c7d4a28d96d0c4daa79e39b3f2a764"}, + {file = "lxml-6.0.3-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:738aef404c862d2c3cd951364ee7175c9d50e8290f5726611c4208c0fba8d186"}, + {file = "lxml-6.0.3-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:5c35e5c3ed300990a46a144d3514465713f812b35dacfa83e928c60db7c90af7"}, + {file = "lxml-6.0.3-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:4ff774b43712b0cf40d9888a5494ca39aefe990c946511cc947b9fddcf74a29b"}, + {file = "lxml-6.0.3-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d20af2784c763928d0d0879cbc5a3739e4d81eefa0d68962d3478bff4c13e644"}, + {file = "lxml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fdb7786ebefaa0dad0d399dfeaf146b370a14591af2f3aea59e06f931a426678"}, + {file = "lxml-6.0.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c71a387ea133481e725079cff22de45593bf0b834824de22829365ab1d2386c9"}, + {file = "lxml-6.0.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:841b89fc3d910d61c7c267db6bb7dc3a8b3dac240edb66220fcdf96fe70a0552"}, + {file = "lxml-6.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:ac2d6cdafa29672d6a604c641bf67ace3fd0735ec6885501a94943379219ddbf"}, + {file = "lxml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:609bf136a7339aeca2bd4268c7cd190f33d13118975fe9964eda8e5138f42802"}, + {file = "lxml-6.0.3-cp314-cp314t-win32.whl", hash = "sha256:bf98f5f87f6484302e7cce4e2ca5af43562902852063d916c3e2f1c115fdce60"}, + {file = "lxml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:d3d65e511e4e656ec67b472110f7a72cbf8547ca15f76fe74cffa4e97412a064"}, + {file = "lxml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:cbc7ce67f85b92db97c92219985432be84dc1ba9a028e68c6933e89551234df2"}, + {file = "lxml-6.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fc6eeeddcee4d985707b3fc29fd6993e256e55798ca600586da68d3f0e04ec5a"}, + {file = "lxml-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9e779a9c3167d2f158c41b6347fbc9e855f6d5561920a33123beb86463c3f46"}, + {file = "lxml-6.0.3-cp38-cp38-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:911dbd0e87729016402e03533117ffe94147bfbcd7902b76af5854c4b437e096"}, + {file = "lxml-6.0.3-cp38-cp38-manylinux_2_28_i686.whl", hash = "sha256:f297e9be3bbe59c31cfe5d0f38534510ed95856c480df3e8721b16d46ccaeeac"}, + {file = "lxml-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2d00902610dd91a970a06d5f1a6e4f2426e04a9fa07209d58e09b345e2269267"}, + {file = "lxml-6.0.3-cp38-cp38-win32.whl", hash = "sha256:46c80f7fd5dd7d8c269f6486b165c8f91c489e323f3a767221fb5b4b2b388d5f"}, + {file = "lxml-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:336925309af5ecd9616acbe1c28d68ee0088839b1d42ae7dcc40ffecff959537"}, + {file = "lxml-6.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:353161f166e76f0d8228f8f5216d110601d143a8b6d0fd869230e12c098a5842"}, + {file = "lxml-6.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8ad05bb1fb4aa20ee201ab2f21c3c7571828e4fad525707437bb1c5f5ab6669"}, + {file = "lxml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:976b56bd0f4ca72280b4569eb227d012a541860f0d6fcc128ce26d22ef82c845"}, + {file = "lxml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:322c825054d02d35755b80dc5c39ba8a71c7c09c43453394a13b9bd8c0abc84c"}, + {file = "lxml-6.0.3-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c46a246df7fbab1f7b018c7eb361585b295ac95dec806158d9ed1e63b477f05"}, + {file = "lxml-6.0.3-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f8f004d5c601eb50ac29a110632595a4f4c50db81c715d46a43de64ef88246cb"}, + {file = "lxml-6.0.3-cp39-cp39-manylinux_2_28_i686.whl", hash = "sha256:33741ec3b4268efffae8ba40f9b64523f4489caf270bde175535e659f03af05a"}, + {file = "lxml-6.0.3-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:773c22d3de2dd5454b0f89b36d366fa0052f0e924656506c40462d8107acd00f"}, + {file = "lxml-6.0.3-cp39-cp39-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b29300eeaadf85df7f2df4bb29cadfb13b9600d1bb8053f35d82d96f9e6d088a"}, + {file = "lxml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:61399a5b01d74ec5f009762c74ab6ebf387e58393fc5d78368e12d4f577fea29"}, + {file = "lxml-6.0.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4723da3e8f4281853c2eaab161b69cc14bc9b0811be4cfa5a1de36df47e0c660"}, + {file = "lxml-6.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:8341c446fca7f6be003c60aededf2f23a53d4881cf7e0bafb9cef69c217ae26a"}, + {file = "lxml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:809726d4c72f1a902556df822c3acc5780053a12a05e16eac999392b30984c06"}, + {file = "lxml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:d1f14c1006722824c3f2158d147e520a82460b47ffdc2a84e444b3d244b65e4d"}, + {file = "lxml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:d8a8129bd502de138cdde22eac3990bde8a4efbafc72596baab67a495c48c974"}, + {file = "lxml-6.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:fddcf558bcb7a40993fb9e3ae3752d5d3a656479c71687799e43063563a2e68a"}, + {file = "lxml-6.0.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:093786037b934ef4747b0e8a0e1599fe7df7dd8246e7f07d43bba1c4c8bd7b84"}, + {file = "lxml-6.0.3-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6364aa77b13e04459df6a9d2b806465287e7540955527e75ebd5fda48532913d"}, + {file = "lxml-6.0.3-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:955550c78afb2be47755bd1b8153724292a5b539cf3f21665b310c145d08e6f8"}, + {file = "lxml-6.0.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a9a79144a8051bc5fbb223fac895b87eb67b361f27b00c2ed4a07ee34246b90"}, + {file = "lxml-6.0.3-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8243937d4673b46da90b4f5ea2627fd26842225e62e885828fdb8133aa1f7b32"}, + {file = "lxml-6.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:5892d2ef99449ebd8e30544af5bc61fd9c30e9e989093a10589766422f6c5e1a"}, + {file = "lxml-6.0.3.tar.gz", hash = "sha256:a1664c5139755df44cab3834f4400b331b02205d62d3fdcb1554f63439bf3372"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml_html_clean"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] + [[package]] name = "markupsafe" version = "2.1.1" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, @@ -1106,6 +1293,7 @@ version = "3.6.2" description = "Python plotting package" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "matplotlib-3.6.2-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:8d0068e40837c1d0df6e3abf1cdc9a34a6d2611d90e29610fa1d2455aeb4e2e5"}, {file = "matplotlib-3.6.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:252957e208c23db72ca9918cb33e160c7833faebf295aaedb43f5b083832a267"}, @@ -1167,6 +1355,7 @@ version = "0.1.6" description = "Inline Matplotlib backend for Jupyter" optional = false python-versions = ">=3.5" +groups = ["main"] files = [ {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, @@ -1181,6 +1370,7 @@ version = "2.0.4" description = "A sane Markdown parser with useful plugins and renderers" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "mistune-2.0.4-py2.py3-none-any.whl", hash = "sha256:182cc5ee6f8ed1b807de6b7bb50155df7b66495412836b9a74c8fbdfc75fe36d"}, {file = "mistune-2.0.4.tar.gz", hash = "sha256:9ee0a66053e2267aba772c71e06891fa8f1af6d4b01d5e84e267b4570d4d9808"}, @@ -1192,6 +1382,7 @@ version = "0.991" description = "Optional static typing for Python" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "mypy-0.991-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab"}, {file = "mypy-0.991-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d"}, @@ -1242,6 +1433,7 @@ version = "0.4.3" description = "Experimental type system extensions for programs checked with the mypy typechecker." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, @@ -1253,6 +1445,7 @@ version = "0.4.8" description = "A web-based notebook environment for interactive computing" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "nbclassic-0.4.8-py3-none-any.whl", hash = "sha256:cbf05df5842b420d5cece0143462380ea9d308ff57c2dc0eb4d6e035b18fbfb3"}, {file = "nbclassic-0.4.8.tar.gz", hash = "sha256:c74d8a500f8e058d46b576a41e5bc640711e1032cf7541dde5f73ea49497e283"}, @@ -1280,7 +1473,7 @@ traitlets = ">=4.2.1" [package.extras] docs = ["myst-parser", "nbsphinx", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-github-alt"] json-logging = ["json-logging"] -test = ["coverage", "nbval", "pytest", "pytest-cov", "pytest-playwright", "pytest-tornasync", "requests", "requests-unixsocket", "testpath"] +test = ["coverage", "nbval", "pytest", "pytest-cov", "pytest-playwright", "pytest-tornasync", "requests", "requests-unixsocket ; sys_platform != \"win32\"", "testpath"] [[package]] name = "nbclient" @@ -1288,6 +1481,7 @@ version = "0.7.2" description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." optional = false python-versions = ">=3.7.0" +groups = ["main"] files = [ {file = "nbclient-0.7.2-py3-none-any.whl", hash = "sha256:d97ac6257de2794f5397609df754fcbca1a603e94e924eb9b99787c031ae2e7c"}, {file = "nbclient-0.7.2.tar.gz", hash = "sha256:884a3f4a8c4fc24bb9302f263e0af47d97f0d01fe11ba714171b320c8ac09547"}, @@ -1310,6 +1504,7 @@ version = "7.2.6" description = "Converting Jupyter Notebooks" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "nbconvert-7.2.6-py3-none-any.whl", hash = "sha256:f933e82fe48b9a421e4252249f6c0a9a9940dc555642b4729f3f1f526bb16779"}, {file = "nbconvert-7.2.6.tar.gz", hash = "sha256:c9c0e4b26326f7658ebf4cda0acc591b9727c4e3ee3ede962f70c11833b71b40"}, @@ -1319,7 +1514,6 @@ files = [ beautifulsoup4 = "*" bleach = "*" defusedxml = "*" -importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""} jinja2 = ">=3.0" jupyter-core = ">=4.7" jupyterlab-pygments = "*" @@ -1348,6 +1542,7 @@ version = "5.7.0" description = "The Jupyter Notebook format" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "nbformat-5.7.0-py3-none-any.whl", hash = "sha256:1b05ec2c552c2f1adc745f4eddce1eac8ca9ffd59bb9fd859e827eaa031319f9"}, {file = "nbformat-5.7.0.tar.gz", hash = "sha256:1d4760c15c1a04269ef5caf375be8b98dd2f696e5eb9e603ec2bf091f9b0d3f3"}, @@ -1368,6 +1563,7 @@ version = "1.5.6" description = "Patch asyncio to allow nested event loops" optional = false python-versions = ">=3.5" +groups = ["main"] files = [ {file = "nest_asyncio-1.5.6-py3-none-any.whl", hash = "sha256:b9a953fb40dceaa587d109609098db21900182b16440652454a146cffb06e8b8"}, {file = "nest_asyncio-1.5.6.tar.gz", hash = "sha256:d267cc1ff794403f7df692964d1d2a3fa9418ffea2a3f6859a439ff482fef290"}, @@ -1379,6 +1575,7 @@ version = "6.5.2" description = "A web-based notebook environment for interactive computing" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "notebook-6.5.2-py3-none-any.whl", hash = "sha256:e04f9018ceb86e4fa841e92ea8fb214f8d23c1cedfde530cc96f92446924f0e4"}, {file = "notebook-6.5.2.tar.gz", hash = "sha256:c1897e5317e225fc78b45549a6ab4b668e4c996fd03a04e938fe5e7af2bfffd0"}, @@ -1405,7 +1602,7 @@ traitlets = ">=4.2.1" [package.extras] docs = ["myst-parser", "nbsphinx", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-github-alt"] json-logging = ["json-logging"] -test = ["coverage", "nbval", "pytest", "pytest-cov", "requests", "requests-unixsocket", "selenium (==4.1.5)", "testpath"] +test = ["coverage", "nbval", "pytest", "pytest-cov", "requests", "requests-unixsocket ; sys_platform != \"win32\"", "selenium (==4.1.5)", "testpath"] [[package]] name = "notebook-shim" @@ -1413,6 +1610,7 @@ version = "0.2.2" description = "A shim layer for notebook traits and config" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "notebook_shim-0.2.2-py3-none-any.whl", hash = "sha256:9c6c30f74c4fbea6fce55c1be58e7fd0409b1c681b075dcedceb005db5026949"}, {file = "notebook_shim-0.2.2.tar.gz", hash = "sha256:090e0baf9a5582ff59b607af523ca2db68ff216da0c69956b62cab2ef4fc9c3f"}, @@ -1430,6 +1628,7 @@ version = "1.23.5" description = "NumPy is the fundamental package for array computing with Python." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "numpy-1.23.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9c88793f78fca17da0145455f0d7826bcb9f37da4764af27ac945488116efe63"}, {file = "numpy-1.23.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e9f4c4e51567b616be64e05d517c79a8a22f3606499941d97bb76f2ca59f982d"}, @@ -1467,6 +1666,7 @@ version = "7.4.0" description = "A decorator to automatically detect mismatch when overriding a method." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "overrides-7.4.0-py3-none-any.whl", hash = "sha256:3ad24583f86d6d7a49049695efe9933e67ba62f0c7625d53c59fa832ce4b8b7d"}, {file = "overrides-7.4.0.tar.gz", hash = "sha256:9502a3cca51f4fac40b5feca985b6703a5c1f6ad815588a7ca9e285b9dca6757"}, @@ -1478,6 +1678,7 @@ version = "21.3" description = "Core utilities for Python packages" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, @@ -1492,6 +1693,7 @@ version = "1.5.2" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pandas-1.5.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e9dbacd22555c2d47f262ef96bb4e30880e5956169741400af8b306bbb24a273"}, {file = "pandas-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e2b83abd292194f350bb04e188f9379d36b8dfac24dd445d5c87575f3beaf789"}, @@ -1524,9 +1726,8 @@ files = [ [package.dependencies] numpy = [ - {version = ">=1.20.3", markers = "python_version < \"3.10\""}, {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, - {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, + {version = ">=1.21.0", markers = "python_version == \"3.10\""}, ] python-dateutil = ">=2.8.1" pytz = ">=2020.1" @@ -1540,6 +1741,7 @@ version = "1.5.0" description = "Utilities for writing pandoc filters in python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] files = [ {file = "pandocfilters-1.5.0-py2.py3-none-any.whl", hash = "sha256:33aae3f25fd1a026079f5d27bdd52496f0e0803b3469282162bafdcbdf6ef14f"}, {file = "pandocfilters-1.5.0.tar.gz", hash = "sha256:0b679503337d233b4339a817bfc8c50064e2eff681314376a47cb582305a7a38"}, @@ -1551,6 +1753,7 @@ version = "0.8.3" description = "A Python Parser" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, @@ -1566,6 +1769,8 @@ version = "4.8.0" description = "Pexpect allows easy control of interactive console applications." optional = false python-versions = "*" +groups = ["main"] +markers = "sys_platform != \"win32\"" files = [ {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, @@ -1580,6 +1785,7 @@ version = "0.7.5" description = "Tiny 'shelve'-like database with concurrency support" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, @@ -1591,6 +1797,7 @@ version = "10.0.1" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "Pillow-10.0.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:8f06be50669087250f319b706decf69ca71fdecd829091a37cc89398ca4dc17a"}, {file = "Pillow-10.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50bd5f1ebafe9362ad622072a1d2f5850ecfa44303531ff14353a4059113b12d"}, @@ -1658,6 +1865,7 @@ version = "2.5.4" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"}, {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"}, @@ -1673,6 +1881,7 @@ version = "1.0.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, @@ -1688,6 +1897,7 @@ version = "0.15.0" description = "Python client for the Prometheus monitoring system." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "prometheus_client-0.15.0-py3-none-any.whl", hash = "sha256:db7c05cbd13a0f79975592d112320f2605a325969b270a94b71dcabc47b931d2"}, {file = "prometheus_client-0.15.0.tar.gz", hash = "sha256:be26aa452490cfcf6da953f9436e95a9f2b4d578ca80094b4458930e5f584ab1"}, @@ -1702,6 +1912,7 @@ version = "3.0.36" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.6.2" +groups = ["main"] files = [ {file = "prompt_toolkit-3.0.36-py3-none-any.whl", hash = "sha256:aa64ad242a462c5ff0363a7b9cfe696c20d55d9fc60c11fd8e632d064804d305"}, {file = "prompt_toolkit-3.0.36.tar.gz", hash = "sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63"}, @@ -1716,6 +1927,7 @@ version = "5.9.4" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] files = [ {file = "psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"}, {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"}, @@ -1734,7 +1946,7 @@ files = [ ] [package.extras] -test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] +test = ["enum34 ; python_version <= \"3.4\"", "ipaddress ; python_version < \"3.0\"", "mock ; python_version < \"3.0\"", "pywin32 ; sys_platform == \"win32\"", "wmi ; sys_platform == \"win32\""] [[package]] name = "ptyprocess" @@ -1742,6 +1954,8 @@ version = "0.7.0" description = "Run a subprocess in a pseudo terminal" optional = false python-versions = "*" +groups = ["main"] +markers = "sys_platform != \"win32\" or os_name != \"nt\"" files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, @@ -1753,6 +1967,7 @@ version = "0.2.2" description = "Safely evaluate AST nodes without side effects" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, @@ -1767,6 +1982,8 @@ version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] +markers = "implementation_name == \"pypy\"" files = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, @@ -1778,6 +1995,7 @@ version = "2.21" description = "C parser in Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] files = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, @@ -1789,13 +2007,14 @@ version = "2.15.0" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "Pygments-2.15.0-py3-none-any.whl", hash = "sha256:77a3299119af881904cd5ecd1ac6a66214b6e9bed1f2db16993b54adede64094"}, {file = "Pygments-2.15.0.tar.gz", hash = "sha256:f7e36cffc4c517fbc252861b9a6e4644ca0e5abadf9a113c72d1358ad09b9500"}, ] [package.extras] -plugins = ["importlib-metadata"] +plugins = ["importlib-metadata ; python_version < \"3.8\""] [[package]] name = "pyparsing" @@ -1803,6 +2022,7 @@ version = "3.0.9" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.6.8" +groups = ["main", "dev"] files = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, @@ -1817,6 +2037,7 @@ version = "0.19.2" description = "Persistent/Functional/Immutable data structures" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "pyrsistent-0.19.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d6982b5a0237e1b7d876b60265564648a69b14017f3b5f908c5be2de3f9abb7a"}, {file = "pyrsistent-0.19.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:187d5730b0507d9285a96fca9716310d572e5464cadd19f22b63a6976254d77a"}, @@ -1848,6 +2069,7 @@ version = "7.2.0" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, @@ -1867,34 +2089,48 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2. [[package]] name = "python-copasi" -version = "4.37.264" +version = "4.46.300" description = "COPASI Python API" optional = false python-versions = "*" -files = [ - {file = "python_copasi-4.37.264-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:feb5e6a08d1845167489f0dc046d2fea659985e61372cf1cea3801d427c74516"}, - {file = "python_copasi-4.37.264-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a3852b1654b030351a5a2f2f12b272dabc779a3effd6ee35bd3b5b8cf8f7cd5d"}, - {file = "python_copasi-4.37.264-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:643012aea17f5818ef8f481f36bda4a7d02fe942ceb7db14e6b0cefd4b776f2d"}, - {file = "python_copasi-4.37.264-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c60e08628037975e07561fb23b573233ea14b9a692ef796648fb24d6b155f660"}, - {file = "python_copasi-4.37.264-cp310-cp310-win32.whl", hash = "sha256:f5bbb453e0c8c1809f77cb27541c0b0be170ce5ace7721db97b76ca0f4993654"}, - {file = "python_copasi-4.37.264-cp310-cp310-win_amd64.whl", hash = "sha256:bf51cb2f515623731d3e4997a22596901fc84c0a94c72ab919223eded99eaac0"}, - {file = "python_copasi-4.37.264-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:17411ea3e3a1c2e807a387809d2bd2ae89759d489235e252f5a44e07e355333b"}, - {file = "python_copasi-4.37.264-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6db60217c9819cd9b91cd062a8e329c3cebacd5c0182032d9c244e50411f9f1"}, - {file = "python_copasi-4.37.264-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:145b952bccfc772846aae4e4817170b2c3b75939c404d6be1072c5a888fb375e"}, - {file = "python_copasi-4.37.264-cp37-cp37m-win32.whl", hash = "sha256:d6995873d25c5f4fe0434b2eaef908d1b2458b7e7536d053262024feb1b9df42"}, - {file = "python_copasi-4.37.264-cp37-cp37m-win_amd64.whl", hash = "sha256:97b18e21569745f0b9e06a84bd95624c959353f8fdc507a4403bee7a5635c317"}, - {file = "python_copasi-4.37.264-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:30c8bd90ab62405c2481d1498e2131bfd2c24fee8b5f21c4c5295cda889d5bc9"}, - {file = "python_copasi-4.37.264-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9b88d6368407e09c37c49bea16c2228b41310deeb326a874a379a27688dc8c4b"}, - {file = "python_copasi-4.37.264-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28ee9f80bb41ee02a0e713c9a672efbdbe141464a44190dcf69c4115393c3a10"}, - {file = "python_copasi-4.37.264-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:790ec25c0e1300002b5f4c2ef54f322e344687fe7886b1fe058b264da24414ba"}, - {file = "python_copasi-4.37.264-cp38-cp38-win32.whl", hash = "sha256:ccd81390bdd4fd6abc8045c42b4f35f08e8eb2154e3fd051d8c8a7ff94b4914d"}, - {file = "python_copasi-4.37.264-cp38-cp38-win_amd64.whl", hash = "sha256:8af99f7b8ceb27034121342b4bde313408b81f8e7213e451fd223d85de515ac9"}, - {file = "python_copasi-4.37.264-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0cc72e904a732d80d1935e38bbb94df762d7726fea9d8245f7d9cb9e6e3c9f0c"}, - {file = "python_copasi-4.37.264-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:03c969f2f44cb6d5be5fc533054dd8281ee805631edb07b4a825a2397dd1fef0"}, - {file = "python_copasi-4.37.264-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad524378a740bb0b49935f194db315045dfa9c457757b084b110a1fc4d475170"}, - {file = "python_copasi-4.37.264-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa4bebd9544903b11162acff4e5629816c0823b068bb1d362a455f411a251389"}, - {file = "python_copasi-4.37.264-cp39-cp39-win32.whl", hash = "sha256:69615d04a8fdd9659f83d73b2ff3145afe4cd878c804930052edab434300cb7c"}, - {file = "python_copasi-4.37.264-cp39-cp39-win_amd64.whl", hash = "sha256:3c6adbdbd4e8f585ce77fcefa952af074aa08ce3ccacf6e91457e09f0dcb837e"}, +groups = ["main"] +files = [ + {file = "python_copasi-4.46.300-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6fd5709ca1a78428a163d1a203fea7a0decc69fcd5f8438ddfa5bbaf2d31981"}, + {file = "python_copasi-4.46.300-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8940425b288cf5f52cd99deb69e848d6ffe99b8b02208cfcc486f19fa4918b1a"}, + {file = "python_copasi-4.46.300-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2fa40e04006d540b6bca1e9cd59e16c15c96854c530c620676519b3edec12d36"}, + {file = "python_copasi-4.46.300-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f01ed153041a37b0c29d5a03c5244e0ff1874eacebe735c670ae140846dc6421"}, + {file = "python_copasi-4.46.300-cp310-cp310-win_amd64.whl", hash = "sha256:9e5d5995ea2ac11845b8e314984390a4cc38bc0ebec226b890fb173c39c64322"}, + {file = "python_copasi-4.46.300-cp310-cp310-win_arm64.whl", hash = "sha256:cb425ef95950f55ade364f7b3fad02df555e2fc061f5dd8b79c7c2e17e2e6307"}, + {file = "python_copasi-4.46.300-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:df42f6d83dfa376ef70ee53d58fb15a22f10aa1b61fd6f52892a9825d3b0044d"}, + {file = "python_copasi-4.46.300-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3109e67caea1fa1318778769ec985c1aa5878c19237e73189115cbd83b665f0e"}, + {file = "python_copasi-4.46.300-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1adaadcfd881f4dd160fdde41a03114c42c1d5205a28b4e4d66dff2f7361d66d"}, + {file = "python_copasi-4.46.300-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99d6561f0b150605d7b2782af121fbf2597786b48e0ca7139d02e2cf2f98cc89"}, + {file = "python_copasi-4.46.300-cp311-cp311-win_amd64.whl", hash = "sha256:bca6e68c47011745edaa06d4391fd102192c758540acc2fa5e5a2d2ff20d2986"}, + {file = "python_copasi-4.46.300-cp311-cp311-win_arm64.whl", hash = "sha256:5b68a8bee537e7b62d883341818cc56356cdaf64356dd50ef20bbc77845c6189"}, + {file = "python_copasi-4.46.300-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c288312bfd4841aa549df92fcf070581c2cd71d7ca1b3f6ffdb9c5c551b35bf"}, + {file = "python_copasi-4.46.300-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d5b3d6ab846e7f7ee1dd6d0bc8ae2df8b3272434ff6b30a4d38fa0745ffc45f5"}, + {file = "python_copasi-4.46.300-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:29ba3be336f892c76df37c0311ce42beea6ff506db0acd1ff2ea93c357403e73"}, + {file = "python_copasi-4.46.300-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:033d21faecc6f981355e6307c1fdebc9029838cf942597caa145d870ff3f445a"}, + {file = "python_copasi-4.46.300-cp312-cp312-win_amd64.whl", hash = "sha256:1e057f94ad6bfe35bf457a7f8b6a3dbb776d44df0772e7e5dc2be54ede0fb072"}, + {file = "python_copasi-4.46.300-cp312-cp312-win_arm64.whl", hash = "sha256:d6fc0cc65a70ed6c23aad200d6501ddf13ea07515e7aab5ea376f354dabf30e1"}, + {file = "python_copasi-4.46.300-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:aba072a1ae075d04148cc37e9493ea79ac0c8474bca06b13c39521f802645a57"}, + {file = "python_copasi-4.46.300-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4355d1289de105bd51b86cd2ef64aeaa3c9921753de173eba37e5225e3268186"}, + {file = "python_copasi-4.46.300-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd98fa69c13c149b4fb86a387ef240615ee5f32b0395a38ad8efaf77eed00ee6"}, + {file = "python_copasi-4.46.300-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6a2a8a45ee9cf8404f950082425cb8f11a53cf70dccc5cb95082ab166d70fe5b"}, + {file = "python_copasi-4.46.300-cp313-cp313-win_amd64.whl", hash = "sha256:63cacffe4a651c42e14d1d3f5dc156602df0c150fde1bcf62f6652043c63efd2"}, + {file = "python_copasi-4.46.300-cp313-cp313-win_arm64.whl", hash = "sha256:c889b184290d6d7ef1cd09daa3b01656c567ef4781cd1c9e1a21b27534661c83"}, + {file = "python_copasi-4.46.300-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e25a9159f8958550a75546e8fe734d621cb616ac108761e98179d948c957bd05"}, + {file = "python_copasi-4.46.300-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1bd8bdfc6e4dc16202a8d867f87012a7861503df952989422cd013e3dc6e8062"}, + {file = "python_copasi-4.46.300-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44d691f5960b72c0265f2c360bb1b74e3ceaafdfe175c13f3f715d18684a6268"}, + {file = "python_copasi-4.46.300-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a08864ab379d893ea13efd4691b6dd924131dc98530180d9df86068cabe4ba1a"}, + {file = "python_copasi-4.46.300-cp314-cp314-win_amd64.whl", hash = "sha256:f79af84864bc12fd7d64050a0d84cf27f8d2a2f3bf7fe3dc36a652873ebe2f3d"}, + {file = "python_copasi-4.46.300-cp314-cp314-win_arm64.whl", hash = "sha256:b858704aa061c059d6a21a505c8657b65bfdbd412724bfe414a4cb83bfbfb595"}, + {file = "python_copasi-4.46.300-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2659dc1a983779e5c8d64bf360f4eb0ca169ad8a362d7639c800a5d6d7e4d529"}, + {file = "python_copasi-4.46.300-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18d878482d15f514be7a84131f576718a11c59869ff7569a7a86b9f8c2744bd5"}, + {file = "python_copasi-4.46.300-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:554f4fa303e1cf9742d084d7a33bdaf74ff0531ae4e3753624226e5c4d11234a"}, + {file = "python_copasi-4.46.300-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f2a928bf697baed5579c8aa005bdb21078c8e6f2798685c0409242e7f8c656e"}, + {file = "python_copasi-4.46.300-cp39-cp39-win_amd64.whl", hash = "sha256:00ce8b8eb18833d4fee3edb4b1db3b18ae3d06569204c87d2a67bc3af61b123e"}, + {file = "python_copasi-4.46.300-cp39-cp39-win_arm64.whl", hash = "sha256:5fa56797ccfa4b2fd8ddd99bcbb353f7e70af3f2369c97cd291d6b7e2ebdc720"}, ] [[package]] @@ -1903,6 +2139,7 @@ version = "2.8.2" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] files = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, @@ -1917,6 +2154,7 @@ version = "2.0.7" description = "A python library adding a json log formatter" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "python-json-logger-2.0.7.tar.gz", hash = "sha256:23e7ec02d34237c5aa1e29a070193a4ea87583bb4e7f8fd06d3de8264c4b2e1c"}, {file = "python_json_logger-2.0.7-py3-none-any.whl", hash = "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd"}, @@ -1928,6 +2166,7 @@ version = "2022.6" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "pytz-2022.6-py2.py3-none-any.whl", hash = "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427"}, {file = "pytz-2022.6.tar.gz", hash = "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"}, @@ -1939,6 +2178,8 @@ version = "305" description = "Python for Window Extensions" optional = false python-versions = "*" +groups = ["main"] +markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\"" files = [ {file = "pywin32-305-cp310-cp310-win32.whl", hash = "sha256:421f6cd86e84bbb696d54563c48014b12a23ef95a14e0bdba526be756d89f116"}, {file = "pywin32-305-cp310-cp310-win_amd64.whl", hash = "sha256:73e819c6bed89f44ff1d690498c0a811948f73777e5f97c494c152b850fad478"}, @@ -1962,6 +2203,8 @@ version = "2.0.9" description = "Pseudo terminal support for Windows from Python." optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "os_name == \"nt\"" files = [ {file = "pywinpty-2.0.9-cp310-none-win_amd64.whl", hash = "sha256:30a7b371446a694a6ce5ef906d70ac04e569de5308c42a2bdc9c3bc9275ec51f"}, {file = "pywinpty-2.0.9-cp311-none-win_amd64.whl", hash = "sha256:d78ef6f4bd7a6c6f94dc1a39ba8fb028540cc39f5cb593e756506db17843125f"}, @@ -1977,6 +2220,7 @@ version = "6.0" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, @@ -2026,6 +2270,7 @@ version = "24.0.1" description = "Python bindings for 0MQ" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "pyzmq-24.0.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:28b119ba97129d3001673a697b7cce47fe6de1f7255d104c2f01108a5179a066"}, {file = "pyzmq-24.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bcbebd369493d68162cddb74a9c1fcebd139dfbb7ddb23d8f8e43e6c87bac3a6"}, @@ -2113,6 +2358,7 @@ version = "5.4.0" description = "Jupyter Qt console" optional = false python-versions = ">= 3.7" +groups = ["main"] files = [ {file = "qtconsole-5.4.0-py3-none-any.whl", hash = "sha256:be13560c19bdb3b54ed9741a915aa701a68d424519e8341ac479a91209e694b2"}, {file = "qtconsole-5.4.0.tar.gz", hash = "sha256:57748ea2fd26320a0b77adba20131cfbb13818c7c96d83fafcb110ff55f58b35"}, @@ -2138,6 +2384,7 @@ version = "2.3.0" description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6)." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "QtPy-2.3.0-py3-none-any.whl", hash = "sha256:8d6d544fc20facd27360ea189592e6135c614785f0dec0b4f083289de6beb408"}, {file = "QtPy-2.3.0.tar.gz", hash = "sha256:0603c9c83ccc035a4717a12908bf6bc6cb22509827ea2ec0e94c2da7c9ed57c5"}, @@ -2155,6 +2402,7 @@ version = "0.1.4" description = "A pure python RFC3339 validator" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] files = [ {file = "rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa"}, {file = "rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b"}, @@ -2169,6 +2417,7 @@ version = "0.1.1" description = "Pure python rfc3986 validator" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] files = [ {file = "rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9"}, {file = "rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055"}, @@ -2180,6 +2429,7 @@ version = "12.6.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.6.3,<4.0.0" +groups = ["main"] files = [ {file = "rich-12.6.0-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e"}, {file = "rich-12.6.0.tar.gz", hash = "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0"}, @@ -2192,21 +2442,86 @@ pygments = ">=2.6.0,<3.0.0" [package.extras] jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] +[[package]] +name = "scipy" +version = "1.15.3" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c"}, + {file = "scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253"}, + {file = "scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f"}, + {file = "scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92"}, + {file = "scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82"}, + {file = "scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40"}, + {file = "scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e"}, + {file = "scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c"}, + {file = "scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13"}, + {file = "scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b"}, + {file = "scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba"}, + {file = "scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65"}, + {file = "scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1"}, + {file = "scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889"}, + {file = "scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982"}, + {file = "scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9"}, + {file = "scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594"}, + {file = "scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb"}, + {file = "scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019"}, + {file = "scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6"}, + {file = "scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477"}, + {file = "scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c"}, + {file = "scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45"}, + {file = "scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49"}, + {file = "scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e"}, + {file = "scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539"}, + {file = "scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed"}, + {file = "scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759"}, + {file = "scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62"}, + {file = "scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb"}, + {file = "scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730"}, + {file = "scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825"}, + {file = "scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7"}, + {file = "scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11"}, + {file = "scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126"}, + {file = "scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163"}, + {file = "scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8"}, + {file = "scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5"}, + {file = "scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e"}, + {file = "scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb"}, + {file = "scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723"}, + {file = "scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb"}, + {file = "scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4"}, + {file = "scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5"}, + {file = "scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca"}, + {file = "scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf"}, +] + +[package.dependencies] +numpy = ">=1.23.5,<2.5" + +[package.extras] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] +doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.19.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.0.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict (>=2.0,<2.1.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + [[package]] name = "send2trash" version = "1.8.2" description = "Send file to trash natively under Mac OS X, Windows and Linux" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +groups = ["main"] files = [ {file = "Send2Trash-1.8.2-py3-none-any.whl", hash = "sha256:a384719d99c07ce1eefd6905d2decb6f8b7ed054025bb0e618919f945de4f679"}, {file = "Send2Trash-1.8.2.tar.gz", hash = "sha256:c132d59fa44b9ca2b1699af5c86f57ce9f4c5eb56629d5d55fbb7a35f84e2312"}, ] [package.extras] -nativelib = ["pyobjc-framework-Cocoa", "pywin32"] -objc = ["pyobjc-framework-Cocoa"] -win32 = ["pywin32"] +nativelib = ["pyobjc-framework-Cocoa ; sys_platform == \"darwin\"", "pywin32 ; sys_platform == \"win32\""] +objc = ["pyobjc-framework-Cocoa ; sys_platform == \"darwin\""] +win32 = ["pywin32 ; sys_platform == \"win32\""] [[package]] name = "shellingham" @@ -2214,6 +2529,7 @@ version = "1.5.4" description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, @@ -2225,6 +2541,7 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -2236,6 +2553,7 @@ version = "1.3.0" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, @@ -2247,6 +2565,7 @@ version = "2.3.2.post1" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, @@ -2258,6 +2577,7 @@ version = "0.6.2" description = "Extract data from python stack frames and tracebacks for informative displays" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "stack_data-0.6.2-py3-none-any.whl", hash = "sha256:cbb2a53eb64e5785878201a97ed7c7b94883f48b87bfb0bbe8b623c74679e4a8"}, {file = "stack_data-0.6.2.tar.gz", hash = "sha256:32d2dd0376772d01b6cb9fc996f3c8b57a357089dec328ed4b6553d037eaf815"}, @@ -2277,6 +2597,7 @@ version = "0.17.1" description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "terminado-0.17.1-py3-none-any.whl", hash = "sha256:8650d44334eba354dd591129ca3124a6ba42c3d5b70df5051b6921d506fdaeae"}, {file = "terminado-0.17.1.tar.gz", hash = "sha256:6ccbbcd3a4f8a25a5ec04991f39a0b8db52dfcd487ea0e578d977e6752380333"}, @@ -2297,6 +2618,7 @@ version = "1.2.1" description = "A tiny CSS parser" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"}, {file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"}, @@ -2315,6 +2637,8 @@ version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version == \"3.10\"" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, @@ -2326,6 +2650,7 @@ version = "6.3.3" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false python-versions = ">= 3.8" +groups = ["main"] files = [ {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:502fba735c84450974fec147340016ad928d29f1e91f49be168c0a4c18181e1d"}, {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:805d507b1f588320c26f7f097108eb4023bbaa984d63176d1652e184ba24270a"}, @@ -2346,6 +2671,7 @@ version = "5.6.0" description = "Traitlets Python configuration system" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "traitlets-5.6.0-py3-none-any.whl", hash = "sha256:1410755385d778aed847d68deb99b3ba30fbbf489e17a1e8cbb753060d5cce73"}, {file = "traitlets-5.6.0.tar.gz", hash = "sha256:10b6ed1c9cedee83e795db70a8b9c2db157bb3778ec4587a349ecb7ef3b1033b"}, @@ -2361,6 +2687,7 @@ version = "0.7.0" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "typer-0.7.0-py3-none-any.whl", hash = "sha256:b5e704f4e48ec263de1c0b3a2387cd405a13767d2f907f44c1a08cbad96f606d"}, {file = "typer-0.7.0.tar.gz", hash = "sha256:ff797846578a9f2a201b53442aedeb543319466870fbe1c701eab66dd7681165"}, @@ -2384,6 +2711,7 @@ version = "2.8.19.14" description = "Typing stubs for python-dateutil" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "types-python-dateutil-2.8.19.14.tar.gz", hash = "sha256:1f4f10ac98bb8b16ade9dbee3518d9ace017821d94b057a425b069f834737f4b"}, {file = "types_python_dateutil-2.8.19.14-py3-none-any.whl", hash = "sha256:f977b8de27787639986b4e28963263fd0e5158942b3ecef91b9335c130cb1ce9"}, @@ -2395,6 +2723,7 @@ version = "4.4.0" description = "Backported and Experimental Type Hints for Python 3.7+" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, @@ -2406,6 +2735,7 @@ version = "1.3.0" description = "RFC 6570 URI Template Processor" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7"}, {file = "uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363"}, @@ -2420,6 +2750,7 @@ version = "0.2.5" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, @@ -2431,6 +2762,7 @@ version = "1.13" description = "A library for working with the color formats defined by HTML and CSS." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "webcolors-1.13-py3-none-any.whl", hash = "sha256:29bc7e8752c0a1bd4a1f03c14d6e6a72e93d82193738fa860cbff59d0fcc11bf"}, {file = "webcolors-1.13.tar.gz", hash = "sha256:c225b674c83fa923be93d235330ce0300373d02885cef23238813b0d5668304a"}, @@ -2446,6 +2778,7 @@ version = "0.5.1" description = "Character encoding aliases for legacy web content" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, @@ -2457,6 +2790,7 @@ version = "1.4.2" description = "WebSocket client for Python with low level API options" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "websocket-client-1.4.2.tar.gz", hash = "sha256:d6e8f90ca8e2dd4e8027c4561adeb9456b54044312dba655e7cae652ceb9ae59"}, {file = "websocket_client-1.4.2-py3-none-any.whl", hash = "sha256:d6b06432f184438d99ac1f456eaf22fe1ade524c3dd16e661142dc54e9cba574"}, @@ -2473,27 +2807,13 @@ version = "4.0.3" description = "Jupyter interactive widgets for Jupyter Notebook" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "widgetsnbextension-4.0.3-py3-none-any.whl", hash = "sha256:7f3b0de8fda692d31ef03743b598620e31c2668b835edbd3962d080ccecf31eb"}, {file = "widgetsnbextension-4.0.3.tar.gz", hash = "sha256:34824864c062b0b3030ad78210db5ae6a3960dfb61d5b27562d6631774de0286"}, ] -[[package]] -name = "zipp" -version = "3.11.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.7" -files = [ - {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, - {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - [metadata] -lock-version = "2.0" -python-versions = "^3.9" -content-hash = "421aec668be9be404488993f689d6eb93084d4b5bd306130fd79656dd2660513" +lock-version = "2.1" +python-versions = "^3.10" +content-hash = "4458d473de4d9f8bc9302ae617aca7a8957d773130068de1c4eda1e971480122" diff --git a/pythonCopasiOpt/vcell-opt/pyproject.toml b/pythonCopasiOpt/vcell-opt/pyproject.toml index 8601fc6291..8aad7e030b 100644 --- a/pythonCopasiOpt/vcell-opt/pyproject.toml +++ b/pythonCopasiOpt/vcell-opt/pyproject.toml @@ -6,13 +6,13 @@ authors = ["Jim Schaff "] readme = "README.md" [tool.poetry.dependencies] -python = "^3.9" -copasi-basico = "0.40" -python-copasi = "4.37.264" +python = "^3.10" +copasi-basico = "^0.86" +python-copasi = "^4.45.298" jupyter = "^1.0.0" typer = {extras = ["all"], version = "^0.7.0"} -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] pytest = "^7.2.0" mypy = "^0.991" diff --git a/pythonCopasiOpt/vcell-opt/tests/vcellopt_test.py b/pythonCopasiOpt/vcell-opt/tests/vcellopt_test.py index 5d5644ea8d..205fb78af4 100644 --- a/pythonCopasiOpt/vcell-opt/tests/vcellopt_test.py +++ b/pythonCopasiOpt/vcell-opt/tests/vcellopt_test.py @@ -44,10 +44,9 @@ def test_run() -> None: # define parameter estimation report format, note that header and footer are omitted to ease parsing # basico.add_report('parest report', task=basico.T.PARAMETER_ESTIMATION, + separator='\t', body=['CN=Root,Vector=TaskList[Parameter Estimation],Problem=Parameter Estimation,Reference=Function Evaluations', - '\\\t', 'CN=Root,Vector=TaskList[Parameter Estimation],Problem=Parameter Estimation,Reference=Best Value', - '\\\t', 'CN=Root,Vector=TaskList[Parameter Estimation],Problem=Parameter Estimation,Reference=Best Parameters' ], ) diff --git a/pythonCopasiOpt/vcell-opt/vcell_opt/optService.py b/pythonCopasiOpt/vcell-opt/vcell_opt/optService.py index a254da56be..7ba8979064 100644 --- a/pythonCopasiOpt/vcell-opt/vcell_opt/optService.py +++ b/pythonCopasiOpt/vcell-opt/vcell_opt/optService.py @@ -43,11 +43,10 @@ def run_command(opt_file: Path = typer.Argument(..., file_okay=True, dir_okay=Fa # define parameter estimation report format, note that header and footer are omitted to ease parsing # basico.add_report('parest report', task=basico.T.PARAMETER_ESTIMATION, + separator='\t', body=[ 'CN=Root,Vector=TaskList[Parameter Estimation],Problem=Parameter Estimation,Reference=Function Evaluations', - '\\\t', 'CN=Root,Vector=TaskList[Parameter Estimation],Problem=Parameter Estimation,Reference=Best Value', - '\\\t', 'CN=Root,Vector=TaskList[Parameter Estimation],Problem=Parameter Estimation,Reference=Best Parameters' ], ) From 98e1a670bbd9bf22d04b94b5512a092e1121fee4 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Sat, 11 Apr 2026 13:12:56 -0400 Subject: [PATCH 33/40] Remove legacy optimization socket server and /api/v0/optimization endpoints Delete the legacy parameter estimation code path that used direct TCP socket connections (port 8877) between vcell-api and vcell-submit. This is replaced by the new Quarkus /api/v1/optimization endpoints with database-backed job tracking and AMQP messaging via Artemis. Removed: - OptimizationRunServerResource.java, OptimizationRunResource.java (vcell-api) - Optimization route registration in VCellApiApplication.java - OptMessage.java socket protocol classes (vcell-core) - Socket server (initOptimizationSocket, OptCommunicationThread) from OptimizationBatchServer - Legacy submitOptProblem (random IDs), optServerStopJob, optServerGetJobStatus - submitOptimization(), getOptRunJson() from VCellApiClient - VCellOptClient.java (unused standalone client) - Port 8877 from docker-compose.yml Co-Authored-By: Claude Opus 4.6 (1M context) --- docker/swarm/docker-compose.yml | 1 - .../org/vcell/rest/VCellApiApplication.java | 6 - .../rest/common/OptimizationRunResource.java | 20 -- .../server/OptimizationRunServerResource.java | 274 ---------------- .../org/vcell/api/client/VCellApiClient.java | 61 ---- .../org/vcell/api/client/VCellOptClient.java | 219 ------------- .../org/vcell/optimization/OptMessage.java | 127 -------- .../batch/opt/OptimizationBatchServer.java | 301 ------------------ .../server/batch/sim/HtcSimulationWorker.java | 1 - 9 files changed, 1010 deletions(-) delete mode 100644 vcell-api/src/main/java/org/vcell/rest/common/OptimizationRunResource.java delete mode 100644 vcell-api/src/main/java/org/vcell/rest/server/OptimizationRunServerResource.java delete mode 100644 vcell-apiclient/src/main/java/org/vcell/api/client/VCellOptClient.java delete mode 100644 vcell-core/src/main/java/org/vcell/optimization/OptMessage.java diff --git a/docker/swarm/docker-compose.yml b/docker/swarm/docker-compose.yml index 92e6debdc3..faf0979e7c 100644 --- a/docker/swarm/docker-compose.yml +++ b/docker/swarm/docker-compose.yml @@ -195,7 +195,6 @@ services: - mongodb_database=test ports: - "${VCELL_DEBUG_PORT_VCELL_SUBMIT}:8000" # java remote debugging - - "8877" volumes: - "${VCELL_SIMDATADIR_HOST}:/simdata" - "${VCELL_SIMDATADIR_SECONDARY_HOST}:/simdata_secondary" diff --git a/vcell-api/src/main/java/org/vcell/rest/VCellApiApplication.java b/vcell-api/src/main/java/org/vcell/rest/VCellApiApplication.java index c086cfdaf8..428acb04fe 100644 --- a/vcell-api/src/main/java/org/vcell/rest/VCellApiApplication.java +++ b/vcell-api/src/main/java/org/vcell/rest/VCellApiApplication.java @@ -91,10 +91,6 @@ public enum AuthenticationPolicy { public static final String RPC = "rpc"; - public static final String OPTIMIZATION = "optimization"; - public static final String RUNOPTIMIZATION = "run"; - public static final String OPTIMIZATIONID = "optimizationid"; - public static final String PUBLICATION = "publication"; public static final String PUBLICATIONID = "publicationid"; @@ -288,8 +284,6 @@ public Restlet createInboundRoot() { rootRouter.attach(PATH_PREFIX+"/"+SCRIPTS, new Directory(getContext(), ROOT_URI)); rootRouter.attach(PATH_PREFIX+"/"+ACCESSTOKENRESOURCE, new AuthenticationTokenRestlet(getContext())); rootRouter.attach(PATH_PREFIX+"/"+WEBAPP, new Directory(getContext(), WEBAPP_URI)); - rootRouter.attach(PATH_PREFIX+"/"+OPTIMIZATION, OptimizationRunServerResource.class); - rootRouter.attach(PATH_PREFIX+"/"+OPTIMIZATION+"/{"+OPTIMIZATIONID+"}", OptimizationRunServerResource.class); rootRouter.attach(PATH_PREFIX+"/"+PUBLICATION, PublicationsServerResource.class); rootRouter.attach(PATH_PREFIX+"/"+PUBLICATION+"/{"+PUBLICATIONID+"}", PublicationServerResource.class); rootRouter.attach(PATH_PREFIX+"/"+BIOMODEL, BiomodelsServerResource.class); diff --git a/vcell-api/src/main/java/org/vcell/rest/common/OptimizationRunResource.java b/vcell-api/src/main/java/org/vcell/rest/common/OptimizationRunResource.java deleted file mode 100644 index 7629a352b0..0000000000 --- a/vcell-api/src/main/java/org/vcell/rest/common/OptimizationRunResource.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.vcell.rest.common; - -import org.json.JSONException; -import org.restlet.ext.json.JsonRepresentation; -import org.restlet.representation.Representation; -import org.restlet.resource.Get; -import org.restlet.resource.Post; - -public interface OptimizationRunResource { - - @Get("json") - public JsonRepresentation get_json(); - - /** - * runs the optimization problem and redirects to the solution. - */ - @Post - public Representation run(Representation OptRunJson) throws JSONException; - -} diff --git a/vcell-api/src/main/java/org/vcell/rest/server/OptimizationRunServerResource.java b/vcell-api/src/main/java/org/vcell/rest/server/OptimizationRunServerResource.java deleted file mode 100644 index 80774f2cdb..0000000000 --- a/vcell-api/src/main/java/org/vcell/rest/server/OptimizationRunServerResource.java +++ /dev/null @@ -1,274 +0,0 @@ -package org.vcell.rest.server; - -import cbit.vcell.resource.PropertyLoader; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.json.JSONObject; -import org.restlet.data.MediaType; -import org.restlet.data.Status; -import org.restlet.ext.json.JsonRepresentation; -import org.restlet.representation.Representation; -import org.restlet.representation.StringRepresentation; -import org.restlet.resource.Post; -import org.restlet.resource.ResourceException; -import org.restlet.resource.ServerResource; -import org.vcell.optimization.OptMessage; -import org.vcell.optimization.jtd.OptProblem; -import org.vcell.rest.VCellApiApplication; -import org.vcell.rest.common.OptimizationRunResource; - -import java.io.EOFException; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.net.Socket; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.util.Hashtable; -import java.util.LinkedHashMap; -import java.util.Map.Entry; - -public class OptimizationRunServerResource extends AbstractServerResource implements OptimizationRunResource { - - private final static Logger lg = LogManager.getLogger(OptimizationRunServerResource.class); - - private static class OptSocketStreams{ - public Socket optSocket; - public ObjectInputStream ois; - public ObjectOutputStream oos; - public String optID; - public OptSocketStreams(Socket optSocket, ObjectInputStream ois, ObjectOutputStream oos) { - super(); - this.optSocket = optSocket; - this.ois = ois; - this.oos = oos; - } - public void closeAll(String optID) { - paramOptActiveSockets.remove(optID); - try{ois.close();}catch(Exception e2) { lg.error(e2.getMessage(), e2); } - try{oos.close();}catch(Exception e2) { lg.error(e2.getMessage(), e2); } - try{optSocket.close();}catch(Exception e2) { lg.error(e2.getMessage(), e2); } - } - public OptMessage.OptResponseMessage sendCommand(OptMessage.OptCommandMessage optCommandMessage) throws IOException,ClassNotFoundException{ - lg.info("sending command "+optCommandMessage+" with ID="+optCommandMessage.optID); - oos.writeObject(optCommandMessage); - lg.info("reading response for command "+optCommandMessage+" with ID="+optCommandMessage.optID); - try { - OptMessage.OptResponseMessage response = (OptMessage.OptResponseMessage) ois.readObject(); - lg.info("responded with "+response+" with ID="+response.optID); - return response; - } catch (EOFException | SocketException e){ - lg.error(e.getMessage(), e); - throw e; - } - } - public static OptSocketStreams create(String ipnum) throws UnknownHostException, IOException { - Socket optSocket = new Socket(ipnum, 8877); - lg.info("Client connected"); - ObjectOutputStream os = new ObjectOutputStream(optSocket.getOutputStream()); - ObjectInputStream objIS = new ObjectInputStream(optSocket.getInputStream()); - lg.info("got streams"); - return new OptSocketStreams(optSocket, objIS, os); - } - } - - private static Hashtable paramOptActiveSockets = new Hashtable<>(); - private static final int MAX_ENTRIES = 10; - private static LinkedHashMap paramOptResults = new LinkedHashMap() { - @Override - protected boolean removeEldestEntry(Entry eldest) { - return size() > MAX_ENTRIES; - } - }; - - private String submitOptProblem(Representation optProblemJsonRep,ServerResource serverResource) throws ResourceException { - synchronized (paramOptActiveSockets) { - if (paramOptActiveSockets.size() >= 100) { - String[] keys = paramOptActiveSockets.keySet().toArray(new String[0]); - for (int i = 0; i < keys.length; i++) { - OptSocketStreams optSocketStreams = paramOptActiveSockets.get(keys[i]); - String optID = keys[i]; - try { - // simple status query - so that we can close the connection and cache the results. - OptMessage.OptResponseMessage response = optSocketStreams.sendCommand(new OptMessage.OptJobQueryCommandMessage(optID)); - if (response instanceof OptMessage.OptErrorResponseMessage) { - OptMessage.OptErrorResponseMessage errorResponse = (OptMessage.OptErrorResponseMessage) response; - throw new RuntimeException("Failed to query optimization ID=" + optID + ": " + errorResponse.errorMessage); - } else if (response instanceof OptMessage.OptJobStatusResponseMessage) { - OptMessage.OptJobStatusResponseMessage statusResponse = (OptMessage.OptJobStatusResponseMessage) response; - if (statusResponse.status == OptMessage.OptJobMessageStatus.FAILED) { - throw new RuntimeException("job for optimization ID=" + optID + " failed: " + statusResponse.statusMessage); - } - } else if (response instanceof OptMessage.OptJobSolutionResponseMessage) { - OptMessage.OptJobSolutionResponseMessage solutionResponse = (OptMessage.OptJobSolutionResponseMessage) response; - paramOptResults.put(optID, new JsonRepresentation(solutionResponse.optRunJsonString)); - break; - } - } catch (Exception e) {//ioError socket closed - lg.error(e.getMessage(), e); - optSocketStreams.closeAll(optSocketStreams.optID); - } - } - if (paramOptActiveSockets.size() >= 100) { - throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "Too many active optimization jobs, try again later"); - } - } - } - // VCellApiApplication application = ((VCellApiApplication)getApplication()); - // User vcellUser = application.getVCellUser(getChallengeResponse(),AuthenticationPolicy.ignoreInvalidCredentials); - if (optProblemJsonRep!=null && optProblemJsonRep.getMediaType().isCompatible(MediaType.APPLICATION_JSON)){ - try { - JsonRepresentation jsonRep = new JsonRepresentation(optProblemJsonRep); - JSONObject json = jsonRep.getJsonObject(); - ObjectMapper objectMapper = new ObjectMapper(); - - // round trip validation - OptProblem optProblem = objectMapper.readValue(json.toString(),OptProblem.class); - String optProblemJsonString = objectMapper.writeValueAsString(optProblem); - - // create new socket resources (remove on failure) - OptSocketStreams optSocketStreams = createOptSocketStreams(); - - OptMessage.OptJobRunCommandMessage runCommand = new OptMessage.OptJobRunCommandMessage(optProblemJsonString); - OptMessage.OptResponseMessage response = optSocketStreams.sendCommand(runCommand); - if (response instanceof OptMessage.OptErrorResponseMessage){ - OptMessage.OptErrorResponseMessage errorResponse = (OptMessage.OptErrorResponseMessage) response; - String errMsg = "opt job run command failed: " + errorResponse.errorMessage; - lg.error(errMsg); - optSocketStreams.closeAll(optSocketStreams.optID); - throw new RuntimeException(errMsg); - } else if (response instanceof OptMessage.OptJobRunResponseMessage){ - OptMessage.OptJobRunResponseMessage runResponse = (OptMessage.OptJobRunResponseMessage) response; - optSocketStreams.optID = runResponse.optID; - lg.info("optimizationJobID="+optSocketStreams.optID+" created socket connect to submit"); - synchronized (paramOptActiveSockets) { - paramOptActiveSockets.put(optSocketStreams.optID, optSocketStreams); - } - return optSocketStreams.optID; - } else { - throw new RuntimeException("unexpected response "+response+" from opt job submission"); - } - } catch (Exception e) { - lg.error(e.getMessage(), e); - throw new ResourceException(Status.SERVER_ERROR_INTERNAL,e.getMessage(), e); - } - }else{ - throw new RuntimeException("unexpected post representation "+optProblemJsonRep); - } - } - - private OptSocketStreams createOptSocketStreams() throws IOException, InterruptedException { - // Server: 127.0.0.11 -// justamq_api.1.16o695tgthpt@dockerbuild | Address: 127.0.0.11#53 -// justamq_api.1.16o695tgthpt@dockerbuild | -// justamq_api.1.16o695tgthpt@dockerbuild | Non-authoritative answer: -// justamq_api.1.16o695tgthpt@dockerbuild | Name: tasks.justamq_submit -// justamq_api.1.16o695tgthpt@dockerbuild | Address: 10.0.7.14 - - //VCELL_SITE defined manually during deploy - //stackname = {"vcell"} + {$VCELL_SITE (from swarm *.config)} - //see vcell/docker/swarm/README.md "CLIENT and SERVER deploy commands" and "To create deploy configuration file" - //tasks.{taskName}, taskName comes from combining stackname + {taskname defined by docker} - //Container gets vcell.server.id from vcell:docker:swarm:deploy.sh and *.config variable VCELL_SITE - //see vcell/docker/swarm/deploy.sh -> echo "env \$(cat $remote_config_file | xargs) docker stack deploy -c $remote_compose_file $stack_name" - //lookup swarm ip number for task - - // - // use optional vcell.submit.service.host property to connect to vcell-submit service (e.g. localhost during dev) - // - String swarmSubmitTaskName = PropertyLoader.getProperty(PropertyLoader.vcellsubmit_service_host, null); - if (swarmSubmitTaskName == null){ - // if not provided, then calculate the DNS name of the docker swarm service for vcell-submit - swarmSubmitTaskName = "tasks."+"vcell"+PropertyLoader.getRequiredProperty(PropertyLoader.vcellServerIDProperty).toLowerCase()+"_submit"; - } - ProcessBuilder pb =new ProcessBuilder("nslookup",swarmSubmitTaskName); - pb.redirectErrorStream(true); - Process process = pb.start(); - java.io.InputStream is = process.getInputStream(); - java.io.InputStreamReader isr = new java.io.InputStreamReader(is); - java.io.BufferedReader br = new java.io.BufferedReader(isr); - String line; - String ipnum = null; - boolean bFound = false; - while ((line = br.readLine()) != null) { - if(line.contains(swarmSubmitTaskName)) { - bFound = true; - }else if (bFound && line.trim().startsWith("Address:")) { - ipnum = line.trim().substring("Address:".length()).trim(); - break; - } - } - br.close(); - int errCode = process.waitFor(); - lg.debug("nslookup errcode="+errCode); - - OptSocketStreams optSocketStreams = OptSocketStreams.create(ipnum); - return optSocketStreams; - } - - private JsonRepresentation queryOptJobStatus(String optID, ServerResource serverResource) throws ResourceException { - synchronized (paramOptActiveSockets) { - if (paramOptResults.containsKey(optID)) {//return cached results, socket already closed - return paramOptResults.remove(optID); - } - boolean bStop = Boolean.parseBoolean(serverResource.getQueryValue("bStop")); - OptSocketStreams optSocketStreams = paramOptActiveSockets.get(optID); - if(optSocketStreams != null) { - try { - if (bStop){ - OptMessage.OptResponseMessage response = optSocketStreams.sendCommand(new OptMessage.OptJobStopCommandMessage(optID)); - if (response instanceof OptMessage.OptErrorResponseMessage){ - OptMessage.OptErrorResponseMessage errorResponse = (OptMessage.OptErrorResponseMessage) response; - throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "Failed to stop optimization ID="+optID+": "+errorResponse.errorMessage); - }else{ - return new JsonRepresentation("stop requested for optimization ID="+optID); - } - } else { - // simple status query - OptMessage.OptResponseMessage response = optSocketStreams.sendCommand(new OptMessage.OptJobQueryCommandMessage(optID)); - if (response instanceof OptMessage.OptErrorResponseMessage){ - OptMessage.OptErrorResponseMessage errorResponse = (OptMessage.OptErrorResponseMessage) response; - throw new RuntimeException("status request failed for optID="+optID+": "+errorResponse.errorMessage); - }else if (response instanceof OptMessage.OptJobStatusResponseMessage){ - OptMessage.OptJobStatusResponseMessage statusResponse = (OptMessage.OptJobStatusResponseMessage) response; - if (statusResponse.progressReportJsonString==null) { - return new JsonRepresentation(statusResponse.status.name() + ":"); - }else{ - return new JsonRepresentation(statusResponse.progressReportJsonString); - } - }else if (response instanceof OptMessage.OptJobSolutionResponseMessage){ - OptMessage.OptJobSolutionResponseMessage solutionResponse = (OptMessage.OptJobSolutionResponseMessage) response; - return new JsonRepresentation(solutionResponse.optRunJsonString); - }else{ - throw new RuntimeException("unexpected response "+response+" from opt job query request - optID="+optID); - } - } - } catch (Exception e) { - lg.error(e.getMessage(), e); - optSocketStreams.closeAll(optID); - throw new ResourceException(Status.SERVER_ERROR_INTERNAL, e.getMessage() + " optimization ID=" + optID, e); - } - }else { - throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "Can't find connection for optimization ID="+optID); - } - } - } - - @Override - @Post - public Representation run(Representation optProblemJson) { - String optID = submitOptProblem(optProblemJson,this); - getResponse().setStatus(Status.SUCCESS_OK); - Representation representation = new StringRepresentation(optID,MediaType.TEXT_PLAIN); - return representation; - } - - @Override - public JsonRepresentation get_json() { - String optID = (String)getRequestAttributes().get(VCellApiApplication.OPTIMIZATIONID); - JsonRepresentation jsonRepresentation = queryOptJobStatus(optID,this); - getResponse().setStatus(Status.SUCCESS_OK); - return jsonRepresentation; - } -} diff --git a/vcell-apiclient/src/main/java/org/vcell/api/client/VCellApiClient.java b/vcell-apiclient/src/main/java/org/vcell/api/client/VCellApiClient.java index 6210b68bac..3d82e77ee2 100644 --- a/vcell-apiclient/src/main/java/org/vcell/api/client/VCellApiClient.java +++ b/vcell-apiclient/src/main/java/org/vcell/api/client/VCellApiClient.java @@ -216,67 +216,6 @@ public EventWrapper[] getEvents(long beginTimestamp) throws IOException { return eventWrappers; } - public String getOptRunJson(String optimizationId,boolean bStop) throws IOException { - - HttpGet httpget = new HttpGet(getApiUrlPrefix()+"/optimization/"+optimizationId+"?bStop="+bStop); - httpget.addHeader("Authorization","Bearer "+httpClientContext.getUserToken(String.class)); - - if (lg.isInfoEnabled()) { - lg.info("Executing request to retrieve optimization run " + httpget.getRequestLine()); - } - - String responseBody = httpclient.execute(httpget, new VCellStringResponseHandler("getOptRunJson()", httpget), httpClientContext); - if (lg.isInfoEnabled()) { - lg.info("returned: "+toStringTruncated(responseBody)); - } - return responseBody; - } - - public String submitOptimization(String optProblemJson) throws IOException, URISyntaxException { - - HttpPost httppost = new HttpPost(getApiUrlPrefix()+"/optimization"); - httppost.addHeader("Authorization", "Bearer "+httpClientContext.getUserToken(String.class)); - - StringEntity input = new StringEntity(optProblemJson); - input.setContentType("application/json"); - httppost.setEntity(input); - - if (lg.isInfoEnabled()) { - lg.info("Executing request to submit optProblem " + httppost.getRequestLine()); - } - - ResponseHandler handler = new ResponseHandler() { - - public String handleResponse(final HttpResponse response) throws ClientProtocolException, IOException { - int status = response.getStatusLine().getStatusCode(); - if (status == 200) { - HttpEntity entity = response.getEntity(); - String message = null; - try (BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent()));){ - message = reader.lines().collect(Collectors.joining()); - } - return message; - } else { - HttpEntity entity = response.getEntity(); - String message = null; - try (BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent()));){ - message = reader.lines().collect(Collectors.joining()); - } - final URI uri = httppost.getURI(); - lg.error("submitOptimization() ("+uri+") failed: response status: " + status + "\nreason: " + message); - throw new ClientProtocolException("submitOptimization() failed: response status: " + status + "\nreason: " + message); - } - } - - }; - String responseUri = httpclient.execute(httppost,handler,httpClientContext); - if (lg.isInfoEnabled()) { - lg.info("returned: "+toStringTruncated(responseUri)); - } - - return responseUri; - } - public void createDefaultQuarkusClient(boolean bIgnoreCertProblems){ apiClient = new ApiClient(){{ if (bIgnoreCertProblems){setHttpClientBuilder(CustomApiClientCode.createInsecureHttpClientBuilder());}; diff --git a/vcell-apiclient/src/main/java/org/vcell/api/client/VCellOptClient.java b/vcell-apiclient/src/main/java/org/vcell/api/client/VCellOptClient.java deleted file mode 100644 index d85feac336..0000000000 --- a/vcell-apiclient/src/main/java/org/vcell/api/client/VCellOptClient.java +++ /dev/null @@ -1,219 +0,0 @@ -package org.vcell.api.client; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URI; -import java.net.URISyntaxException; -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.util.Locale; -import java.util.stream.Collectors; - -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpHost; -import org.apache.http.HttpResponse; -import org.apache.http.ProtocolException; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.HttpResponseException; -import org.apache.http.client.ResponseHandler; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.DefaultRedirectStrategy; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.util.EntityUtils; -import org.apache.http.util.TextUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * This example demonstrates the use of the {@link ResponseHandler} to simplify - * the process of processing the HTTP response and releasing associated - * resources. - */ -public class VCellOptClient { - - private static final Logger lg = LogManager.getLogger(VCellOptClient.class); - private HttpHost httpHost; - private CloseableHttpClient httpclient; - private HttpClientContext httpClientContext; - - // Create a custom response handler - public static class VCellStringResponseHandler implements ResponseHandler { - - private final String methodCallString; - private final HttpGet httpget; - private final HttpPost httppost; - - public VCellStringResponseHandler(String methodCallString, HttpGet httpget) { - this.methodCallString = methodCallString; - this.httpget = httpget; - this.httppost = null; - } - - public VCellStringResponseHandler(String methodCallString, HttpPost httppost) { - this.methodCallString = methodCallString; - this.httpget = null; - this.httppost = httppost; - } - - @Override - public String handleResponse(HttpResponse response) throws HttpResponseException, IOException { - int status = response.getStatusLine().getStatusCode(); - if (status >= 200 && status < 300) { - HttpEntity entity = response.getEntity(); - return entity != null ? EntityUtils.toString(entity) : null; - } else { - HttpEntity entity = response.getEntity(); - String message = null; - try (BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent()));){ - message = reader.lines().collect(Collectors.joining()); - } - final URI uri; - if (httpget!=null) { - uri = httpget.getURI(); - }else { - uri = httppost.getURI(); - } - lg.error(methodCallString+" ("+uri+") failed: response status: " + status + "\nreason: " + message); - throw new HttpResponseException(status, methodCallString+" failed: response status: " + status + "\nreason: " + message); - } - } - } - - public VCellOptClient(String host, int port) throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException{ - this.httpHost = new HttpHost(host,port,"http"); - initClient(); - } - - private void initClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { - - HttpClientBuilder httpClientBuilder = HttpClients.custom(); - httpclient = httpClientBuilder.setRedirectStrategy(new DefaultRedirectStrategy()).build(); - httpClientContext = HttpClientContext.create(); - } - - public void close() throws IOException { - if (httpclient!=null){ - httpclient.close(); - } - } - - public String getOptRunJson(String optimizationId) throws IOException { - HttpGet httpget = new HttpGet("http://"+httpHost.getHostName()+":"+httpHost.getPort()+"/optimization/"+optimizationId); - - if (lg.isInfoEnabled()) { - lg.info("Executing request to retrieve optimization run " + httpget.getRequestLine()); - } - - String responseBody = httpclient.execute(httpget, new VCellStringResponseHandler("getOptRunJson()", httpget), httpClientContext); - if (lg.isInfoEnabled()) { - lg.info("returned: "+toStringTruncated(responseBody)); - } - return responseBody; - } - - public String submitOptimization(String optProblemJson) throws IOException, URISyntaxException { - - HttpPost httppost = new HttpPost("http://"+httpHost.getHostName()+":"+httpHost.getPort()+"/optimization"); - StringEntity input = new StringEntity(optProblemJson); - input.setContentType("application/json"); - httppost.setEntity(input); - - if (lg.isInfoEnabled()) { - lg.info("Executing request to submit optProblem " + httppost.getRequestLine()); - } - - ResponseHandler handler = new ResponseHandler() { - - public String handleResponse(final HttpResponse response) throws ClientProtocolException, IOException { - int status = response.getStatusLine().getStatusCode(); - if (status == 202) { - HttpEntity entity = response.getEntity(); - if (lg.isInfoEnabled()) { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent()));){ - lg.info("optimizationId = "+reader.readLine()); - } - } - final Header locationHeader = response.getFirstHeader("location"); - if (locationHeader == null) { - // got a redirect response, but no location header - throw new ClientProtocolException( - "Received redirect response " + response.getStatusLine() - + " but no location header"); - } - final String location = locationHeader.getValue(); - URI uri = createLocationURI(location); - return uri.toString(); - } else { - HttpEntity entity = response.getEntity(); - String message = null; - try (BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent()));){ - message = reader.lines().collect(Collectors.joining()); - } - final URI uri = httppost.getURI(); - lg.error("submitOptimization() ("+uri+") failed: response status: " + status + "\nreason: " + message); - throw new ClientProtocolException("submitOptimization() failed: response status: " + status + "\nreason: " + message); - } - } - - }; - String responseUri = httpclient.execute(httppost,handler,httpClientContext); - if (lg.isInfoEnabled()) { - lg.info("returned: "+toStringTruncated(responseUri)); - } - - String optimizationId = responseUri.substring(responseUri.lastIndexOf('/') + 1); - return optimizationId; - } - - /** - * from org.apache.http.impl.client.DefaultRedirectStrategy - * - * @param location - * @return - * @throws ProtocolException - */ - private URI createLocationURI(final String location) throws ClientProtocolException { - try { - final URIBuilder b = new URIBuilder(new URI(location).normalize()); - final String host = b.getHost(); - if (host != null) { - b.setHost(host.toLowerCase(Locale.US)); - } - final String path = b.getPath(); - if (TextUtils.isEmpty(path)) { - b.setPath("/"); - } - return b.build(); - } catch (final URISyntaxException ex) { - throw new ClientProtocolException("Invalid redirect URI: " + location, ex); - } - } - - - - private static String toStringTruncated(Object obj) { - return toStringTruncated(obj, 30); - } - - private static String toStringTruncated(Object obj, int maxlength) { - if (obj == null) { - return "null"; - } - String str = obj.toString(); - if (str.length() <= maxlength) { - return str; - }else { - return str.substring(0, maxlength-4)+"..."; - } - } - -} diff --git a/vcell-core/src/main/java/org/vcell/optimization/OptMessage.java b/vcell-core/src/main/java/org/vcell/optimization/OptMessage.java deleted file mode 100644 index 9277052646..0000000000 --- a/vcell-core/src/main/java/org/vcell/optimization/OptMessage.java +++ /dev/null @@ -1,127 +0,0 @@ -package org.vcell.optimization; - -import java.io.Serializable; - -public class OptMessage implements Serializable { - public final String optID; - public OptMessage(String optID){ - this.optID = optID; - } - - public static class OptCommandMessage extends OptMessage { - public OptCommandMessage(String optID){ - super(optID); - } - } - - public static class OptResponseMessage extends OptMessage { - public final OptCommandMessage commandMessage; - public OptResponseMessage(OptCommandMessage commandMessage){ - super((commandMessage!=null)?commandMessage.optID:null); - this.commandMessage = commandMessage; - } - public OptResponseMessage(String optID, OptCommandMessage commandMessage){ - super(optID); - this.commandMessage = commandMessage; - } - } - - public static class OptErrorResponseMessage extends OptResponseMessage { - public final String errorMessage; - public OptErrorResponseMessage(OptCommandMessage optCommandMessage, String errorMessage){ - super(optCommandMessage); - this.errorMessage = errorMessage; - } - public String toString() { - return super.toString() + "(id="+optID+", errorMessage='"+errorMessage+"')"; - } - } - - // - // job submit command/response - // - public static class OptJobRunCommandMessage extends OptCommandMessage { - public final String optProblemJsonString; - public OptJobRunCommandMessage(String optProblemJsonString) { - super(null); - this.optProblemJsonString = optProblemJsonString; - } - public String toString() { - return super.toString() + "(id="+optID+"')"; - } - } - public static class OptJobRunResponseMessage extends OptResponseMessage { - public OptJobRunResponseMessage(String optID, OptJobRunCommandMessage optJobRunCommandMessage){ - super(optID, optJobRunCommandMessage); - } - public String toString() { - return super.toString() + "(id="+optID+")"; - } - } - - // - // job stop command/response - // - public static class OptJobStopCommandMessage extends OptCommandMessage { - public OptJobStopCommandMessage(String optID){ - super(optID); - } - public String toString() { - return super.toString() + "(id="+optID+"')"; - } - } - public static class OptJobStopResponseMessage extends OptResponseMessage { - public OptJobStopResponseMessage(OptJobStopCommandMessage optJobStopCommandMessage){ - super(optJobStopCommandMessage); - } - public String toString() { - return super.toString() + "(id="+optID+"')"; - } - } - - // - // job status command/response - // - public static class OptJobQueryCommandMessage extends OptCommandMessage { - public OptJobQueryCommandMessage(String optID){ - super(optID); - } - public String toString() { - return super.toString() + "(id="+optID+"')"; - } - } - public enum OptJobMessageStatus { - QUEUED, - FAILED, - COMPLETE, - RUNNING - } - public static class OptJobStatusResponseMessage extends OptResponseMessage { - public final OptJobMessageStatus status; - public final String statusMessage; - public final String progressReportJsonString; - - public OptJobStatusResponseMessage(OptJobQueryCommandMessage optJobQueryCommandMessage, - OptJobMessageStatus status, String statusMessage, - String progressReportJsonString) { - super(optJobQueryCommandMessage); - this.status = status; - this.statusMessage = statusMessage; - this.progressReportJsonString = progressReportJsonString; - } - public String toString() { - return super.toString() + "(id="+optID+", status='"+status+", statusMessage='"+statusMessage+"')"; - } - } - public static class OptJobSolutionResponseMessage extends OptResponseMessage { - public final String optRunJsonString; - public OptJobSolutionResponseMessage(OptJobQueryCommandMessage optJobQueryCommandMessage, - String optRunJsonString) { - super(optJobQueryCommandMessage); - this.optRunJsonString = optRunJsonString; - } - public String toString() { - return super.toString() + "(id="+optID+"')"; - } - } -} diff --git a/vcell-server/src/main/java/cbit/vcell/message/server/batch/opt/OptimizationBatchServer.java b/vcell-server/src/main/java/cbit/vcell/message/server/batch/opt/OptimizationBatchServer.java index bdb1dc3dc1..f0538e5cf3 100644 --- a/vcell-server/src/main/java/cbit/vcell/message/server/batch/opt/OptimizationBatchServer.java +++ b/vcell-server/src/main/java/cbit/vcell/message/server/batch/opt/OptimizationBatchServer.java @@ -1,19 +1,15 @@ package cbit.vcell.message.server.batch.opt; -import cbit.vcell.message.server.htc.HtcJobStatus; import cbit.vcell.message.server.htc.HtcProxy; import cbit.vcell.resource.PropertyLoader; import cbit.vcell.server.HtcJobID; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.vcell.optimization.CopasiUtils; import org.vcell.optimization.OptJobStatus; -import org.vcell.optimization.OptMessage; import org.vcell.optimization.OptRequestMessage; import org.vcell.optimization.OptStatusMessage; import org.vcell.optimization.jtd.OptProblem; -import org.vcell.optimization.jtd.OptProgressReport; import org.vcell.optimization.jtd.Vcellopt; import org.vcell.util.exe.ExecutableException; @@ -21,176 +17,12 @@ import org.apache.activemq.ActiveMQConnectionFactory; import java.io.*; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketException; -import java.util.Arrays; -import java.util.Random; public class OptimizationBatchServer { private final static Logger lg = LogManager.getLogger(OptimizationBatchServer.class); - private Random random = new Random(System.currentTimeMillis()); - private ServerSocket optimizationServersocket; private HtcProxy.HtcProxyFactory htcProxyFactory = null; - private static class OptServerJobInfo { - private String optID; - private HtcProxy.HtcJobInfo htcJobInfo; - - public OptServerJobInfo(String optID, HtcProxy.HtcJobInfo htcJobInfo) { - super(); - this.optID = optID; - this.htcJobInfo = htcJobInfo; - } - - public String getOptID() { - return optID; - } - - public HtcProxy.HtcJobInfo getHtcJobInfo() { - return htcJobInfo; - } - } - - public class OptCommunicationThread extends Thread { - private final Socket optSocket; - - public OptCommunicationThread(Socket optSocket){ - super(); - setName("optCommunicationThread"); - this.optSocket = optSocket; - } - - @Override - public void run() { - OptServerJobInfo optServerJobInfo = null; - // let the client socket close the connection - keep listening as long as the client is active - try (ObjectInputStream is = new ObjectInputStream(optSocket.getInputStream()); - ObjectOutputStream oos = new ObjectOutputStream(optSocket.getOutputStream()); - Socket myOptSocket = optSocket) { - while (true) { - OptMessage.OptCommandMessage optCommandMessage = null; - try { - optCommandMessage = (OptMessage.OptCommandMessage) is.readObject(); - if (optServerJobInfo != null && !optCommandMessage.optID.equals(optServerJobInfo.getOptID())) { - String errMsg = "command optID=" + optCommandMessage.optID + " doesn't match socket optID=" + optServerJobInfo.optID; - oos.writeObject(new OptMessage.OptErrorResponseMessage(optCommandMessage, errMsg)); - oos.flush(); - lg.error(errMsg); - } else if (optCommandMessage instanceof OptMessage.OptJobStopCommandMessage) { - OptMessage.OptJobStopCommandMessage stopRequest = (OptMessage.OptJobStopCommandMessage) optCommandMessage; - optServerStopJob(optServerJobInfo); - oos.writeObject(new OptMessage.OptJobStopResponseMessage(stopRequest)); - oos.flush(); - lg.info("send stop request to batch system for job optID=" + optCommandMessage.optID); - } else if (optCommandMessage instanceof OptMessage.OptJobQueryCommandMessage) { - OptMessage.OptJobQueryCommandMessage jobQuery = (OptMessage.OptJobQueryCommandMessage) optCommandMessage; - Vcellopt optRun = getOptResults(optServerJobInfo.getOptID()); - if (optRun != null) { - // job is done, return results - ObjectMapper objectMapper = new ObjectMapper(); - String optRunJsonString = objectMapper.writeValueAsString(optRun); - oos.writeObject(new OptMessage.OptJobSolutionResponseMessage(jobQuery, optRunJsonString)); - oos.flush(); - lg.info("returned solution for job optID=" + optCommandMessage.optID); - } - // else ask the batch system for the status - HtcJobStatus htcJobStatus = optServerGetJobStatus(optServerJobInfo.getHtcJobInfo()); - if (htcJobStatus == null) {//pending - oos.writeObject(new OptMessage.OptJobStatusResponseMessage(jobQuery, OptMessage.OptJobMessageStatus.QUEUED, "queued", null)); - oos.flush(); - lg.info("returned status of " + OptMessage.OptJobMessageStatus.QUEUED + " for job optID=" + optCommandMessage.optID); - } else { - if (htcJobStatus.isFailed()) { - String errorMsg = "slurm job " + optServerJobInfo.getHtcJobInfo().getHtcJobID() + " failed"; - oos.writeObject(new OptMessage.OptJobStatusResponseMessage( - jobQuery, OptMessage.OptJobMessageStatus.FAILED, errorMsg,null)); - oos.flush(); - lg.error(errorMsg); - throw new Exception(errorMsg); - } else if (htcJobStatus.isComplete()) { // but file not found yet - String optID = optCommandMessage.optID; - String errMsg = "job optID=" + optID + " status is COMPLETE but result file "+generateOptOutputFilePath(optID)+" not found yet"; - String progressReportJsonString = getProgressReportJsonString(optID); - oos.writeObject(new OptMessage.OptJobStatusResponseMessage( - jobQuery, OptMessage.OptJobMessageStatus.RUNNING, errMsg, progressReportJsonString)); - oos.flush(); - lg.error(errMsg); - } else {//running - String optID = optCommandMessage.optID; - String msg = "slurm job " + optServerJobInfo.getHtcJobInfo().getHtcJobID() + " running"; - String progressReportJsonString = getProgressReportJsonString(optID); - if (progressReportJsonString == null) { - lg.warn("failed to read progress report for optID="+optID); - } - oos.writeObject(new OptMessage.OptJobStatusResponseMessage( - jobQuery, OptMessage.OptJobMessageStatus.RUNNING, msg, progressReportJsonString)); - oos.flush(); - lg.info(msg); - } - } - } else if (optCommandMessage instanceof OptMessage.OptJobRunCommandMessage) { - OptMessage.OptJobRunCommandMessage runCommand = (OptMessage.OptJobRunCommandMessage) optCommandMessage; - lg.info("submitting optimization job"); - ObjectMapper objectMapper = new ObjectMapper(); - OptProblem optProblem = objectMapper.readValue(runCommand.optProblemJsonString, OptProblem.class); - optServerJobInfo = submitOptProblem(optProblem); - String optID = optServerJobInfo.getOptID(); - oos.writeObject(new OptMessage.OptJobRunResponseMessage(optID, runCommand)); - oos.flush(); - } else { - throw new Exception("Unexpected command " + optCommandMessage); - } - } catch (SocketException | EOFException e) { - String optID = (optServerJobInfo!=null) ? optServerJobInfo.optID : "null"; - String errMsg = "Socket exception - shutting down thread for optID=" + optID + ": " + e.getMessage(); - lg.error(errMsg, e); - return; - } catch (Exception e) { - String optID = (optServerJobInfo!=null) ? optServerJobInfo.optID : "null"; - String errMsg = "error processing command for optID=" + optID + ": " + e.getMessage(); - lg.error(errMsg, e); - try { - oos.writeObject(new OptMessage.OptErrorResponseMessage(optCommandMessage, errMsg)); - oos.flush(); - } catch (Exception e2) { - lg.error(e2.getMessage(), e2); - } - } - } - } catch (Exception e) { - lg.error(e.getMessage(), e); - } finally { - //cleanup - try { - if (optServerJobInfo != null && optServerJobInfo.getOptID() != null) { - File optDir = generateOptimizeDirName(optServerJobInfo.getOptID()); - if (optDir.exists()) { -// generateOptProblemFilePath(optServerJobInfo.getOptID()).delete(); -// generateOptOutputFilePath(optServerJobInfo.getOptID()).delete(); -// generateOptInterresultsFilePath(optServerJobInfo.getOptID()).delete(); - optDir.delete(); - } - } - }catch (Exception e2){ - lg.error(e2.getMessage(), e2); - } - } - } - } - - private String getProgressReportJsonString(String optID) throws IOException { - String progressReportJsonString = null; - OptProgressReport progressReport = getProgressReport(optID); - lg.info(CopasiUtils.progressReportString(progressReport)); - if (progressReport != null){ - ObjectMapper objectMapper = new ObjectMapper(); - progressReportJsonString = objectMapper.writeValueAsString(progressReport); - } - return progressReportJsonString; - } - public OptimizationBatchServer(HtcProxy.HtcProxyFactory htcProxyFactory){ this.htcProxyFactory = htcProxyFactory; } @@ -199,27 +31,6 @@ private HtcProxy getHtcProxy() { return htcProxyFactory.getHtcProxy(); } - public void initOptimizationSocket() { - Thread optThread = new Thread(new Runnable() { - @Override - public void run() { - try { - optimizationServersocket = new ServerSocket(8877); - while (true) { - Socket optSocket = optimizationServersocket.accept(); - OptCommunicationThread optCommunicationThread = new OptCommunicationThread(optSocket); - optCommunicationThread.setDaemon(true); - optCommunicationThread.start(); - } - } catch (Exception e) { - lg.error(e.getMessage(), e); - } - } - }); - optThread.setDaemon(true); - optThread.start(); - } - /** * Initialize a JMS queue listener on "opt-request" for cross-protocol messaging with vcell-rest (AMQP 1.0). * Receives submit/stop commands as JSON text messages, dispatches to SLURM, and sends status updates @@ -366,116 +177,4 @@ private void sendStatusMessage(Session session, MessageProducer producer, Object } } - private Vcellopt getOptResults(String optID) throws IOException { - File f = generateOptOutputFilePath(optID); - if (f.exists()) {// opt job done - ObjectMapper objectMapper = new ObjectMapper(); - return objectMapper.readValue(f,Vcellopt.class); - } - return null; - } - - private OptProgressReport getProgressReport(String optID) throws IOException { - File f = generateOptReportFilePath(optID); - if (f.exists()) { // opt has report (may still be open for reading) - return CopasiUtils.readProgressReportFromCSV(f); - }else { - return null; - } - } - - private OptServerJobInfo submitOptProblem(OptProblem optProblem) throws IOException, ExecutableException { - HtcProxy htcProxyClone = getHtcProxy().cloneThreadsafe(); - File htcLogDirExternal = new File(PropertyLoader.getRequiredProperty(PropertyLoader.htcLogDirExternal)); - File htcLogDirInternal = new File(PropertyLoader.getRequiredProperty(PropertyLoader.htcLogDirInternal)); - int optID = random.nextInt(1000000); - String optSubFileName = generateOptFilePrefix(optID+"")+".sub"; - File sub_file_external = new File(htcLogDirExternal, optSubFileName); - File sub_file_internal = new File(htcLogDirInternal, optSubFileName); - File optProblemFile = generateOptProblemFilePath(optID+""); - File optOutputFile = generateOptOutputFilePath(optID+""); - File optReportFile = generateOptReportFilePath(optID+""); - CopasiUtils.writeOptProblem(optProblemFile, optProblem);//save param optimization problem to user dir - //make sure all can read and write - File optDir = generateOptimizeDirName(optID+""); - optDir.setReadable(true,false); - optDir.setWritable(true,false); - - String slurmOptJobName = generateOptFilePrefix(optID+""); - HtcJobID htcJobID = htcProxyClone.submitOptimizationJob(slurmOptJobName, sub_file_internal, sub_file_external,optProblemFile,optOutputFile,optReportFile); - return new OptServerJobInfo(optID+"", new HtcProxy.HtcJobInfo(htcJobID, slurmOptJobName)); - } - private void optServerStopJob(OptServerJobInfo optServerJobInfo) { - try { - HtcProxy htcProxyClone = getHtcProxy().cloneThreadsafe(); - htcProxyClone.killJobSafe(optServerJobInfo.getHtcJobInfo()); -// CommandOutput commandOutput = htcProxyClone.getCommandService().command(new String[] {"scancel",optServerJobInfo.htcJobID.getJobNumber()+""}); -// return commandOutput.getExitStatus()==0; - } catch (Exception e) { - lg.error(e.getMessage(), e); - } - } - private HtcJobStatus optServerGetJobStatus(HtcProxy.HtcJobInfo htcJobInfo) { - HtcProxy htcProxyClone = getHtcProxy().cloneThreadsafe(); - try { - return htcProxyClone.getJobStatus(Arrays.asList(new HtcProxy.HtcJobInfo[] {htcJobInfo})).get(htcJobInfo); - } catch (Exception e) { - lg.error(e.getMessage(), e); - return null; - } - } - -// private static boolean hackFileExists(File watchThisFile) { -// try { -// //Force container bind mount to update file status -// ProcessBuilder pb = new ProcessBuilder("sh","-c","ls "+watchThisFile.getAbsolutePath()+"*"); -// pb.redirectErrorStream(true); -// Process p = pb.start(); -// BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); -//// StringBuffer sb = new StringBuffer(); -// String line = null; -// while((line = br.readLine()) != null) { -// //sb.append(line+"\n"); -// System.out.println("'"+line+"'"); -// if(line.trim().startsWith("ls: ")) { -//// System.out.println("false"); -// break; -// }else if(line.trim().equals(watchThisFile.getAbsolutePath())) { -//// System.out.println("true"); -// return true; -// } -// } -// p.waitFor(10, TimeUnit.SECONDS); -// br.close(); -//// System.out.println("false"); -// } catch (Exception e) { -// lg.error(e.getMessage(), e); -// } -// return false; -// } - - private File generateOptimizeDirName(String optID) { - File primaryUserDirInternal = new File(PropertyLoader.getRequiredProperty(PropertyLoader.primarySimDataDirInternalProperty)); - File optProblemDir = new File(primaryUserDirInternal,"parest_data"); - optProblemDir.mkdir(); - return optProblemDir; - } - private File generateOptOutputFilePath(String optID) { - String optOutputFileName = generateOptFilePrefix(optID)+"_optRun.json"; - return new File(generateOptimizeDirName(optID), optOutputFileName); - } - private File generateOptReportFilePath(String optID) { - String optOutputFileName = generateOptFilePrefix(optID)+"_optReport.txt"; - return new File(generateOptimizeDirName(optID), optOutputFileName); - } - private File generateOptProblemFilePath(String optID) { - String optOutputFileName = generateOptFilePrefix(optID)+"_optProblem.json"; - return new File(generateOptimizeDirName(optID), optOutputFileName); - } -// private File generateOptInterresultsFilePath(String optID) { -// return new File(generateOptimizeDirName(optID), "interresults.txt"); -// } - private String generateOptFilePrefix(String optID) { - return "CopasiParest_"+optID; - } } \ No newline at end of file diff --git a/vcell-server/src/main/java/cbit/vcell/message/server/batch/sim/HtcSimulationWorker.java b/vcell-server/src/main/java/cbit/vcell/message/server/batch/sim/HtcSimulationWorker.java index 322e152e95..dd39930b15 100644 --- a/vcell-server/src/main/java/cbit/vcell/message/server/batch/sim/HtcSimulationWorker.java +++ b/vcell-server/src/main/java/cbit/vcell/message/server/batch/sim/HtcSimulationWorker.java @@ -110,7 +110,6 @@ public final String getJobSelector() { public void init() { initQueueConsumer(); - optimizationBatchServer.initOptimizationSocket(); // Start JMS queue listener for optimization requests from vcell-rest (via Artemis broker) String artemisHost = PropertyLoader.getRequiredProperty(PropertyLoader.jmsArtemisHostInternal); From fb928f90e3df5b25eda251270d1a7244f33d2c56 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Sat, 11 Apr 2026 13:16:16 -0400 Subject: [PATCH 34/40] Remove dead vcell.submit.service.host property and Dockerfile reference This property was only used by the deleted OptimizationRunServerResource to find the submit service for socket connections on port 8877. The corresponding submit_service_host config in vcell-fluxcd api.env files and port 8877 in submit.yaml should also be removed. Co-Authored-By: Claude Opus 4.6 (1M context) --- docker/build/Dockerfile-api-dev | 2 -- .../src/main/java/cbit/vcell/resource/PropertyLoader.java | 1 - 2 files changed, 3 deletions(-) diff --git a/docker/build/Dockerfile-api-dev b/docker/build/Dockerfile-api-dev index da96024182..0f24a8a90f 100644 --- a/docker/build/Dockerfile-api-dev +++ b/docker/build/Dockerfile-api-dev @@ -69,7 +69,6 @@ ENV softwareVersion=SOFTWARE-VERSION-NOT-SET \ ssl_ignoreCertProblems=false \ serverPrefixV0="server-path-prefix-v0-not-set" \ protocol="https" \ - submit_service_host="submit" \ workingDir="/usr/local/app" ENV dbpswdfile=/run/secrets/dbpswd \ @@ -117,7 +116,6 @@ ENTRYPOINT java \ -Dvcell.smtp.emailAddress="${smtp_emailaddress}" \ -Dvcell.ssl.ignoreHostMismatch="${ssl_ignoreHostMismatch}" \ -Dvcell.ssl.ignoreCertProblems="${ssl_ignoreCertProblems}" \ - -Dvcell.submit.service.host="${submit_service_host}" \ -Dvcellapi.privateKey.file="${vcellapi_privatekeyfile}" \ -Dvcellapi.publicKey.file="${vcellapi_publickeyfile}" \ -cp "./lib/*" org.vcell.rest.VCellApiMain \ diff --git a/vcell-core/src/main/java/cbit/vcell/resource/PropertyLoader.java b/vcell-core/src/main/java/cbit/vcell/resource/PropertyLoader.java index ee19596623..56a97cea8d 100644 --- a/vcell-core/src/main/java/cbit/vcell/resource/PropertyLoader.java +++ b/vcell-core/src/main/java/cbit/vcell/resource/PropertyLoader.java @@ -258,7 +258,6 @@ public static void setConfigProvider(VCellConfigProvider configProvider) { public static final String vcellSMTPPort = record("vcell.smtp.port",ValueType.GEN); public static final String vcellSMTPEmailAddress = record("vcell.smtp.emailAddress",ValueType.GEN); - public static final String vcellsubmit_service_host = record("vcell.submit.service.host",ValueType.GEN); public static final String javaSimulationExecutable = record("vcell.javaSimulation.executable",ValueType.GEN); public static final String simulationPreprocessor = record("vcell.simulation.preprocessor",ValueType.GEN); public static final String simulationPostprocessor = record("vcell.simulation.postprocessor",ValueType.GEN); From a7c832b5d0ba42a3b49ea2779c7dba0780f985cc Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Sat, 11 Apr 2026 13:52:03 -0400 Subject: [PATCH 35/40] Rewrite parameter estimation design doc as maintenance reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove migration-specific content (commit history, decommissioning plan, implementation status tracking). Legacy code has been removed — the doc now describes the current architecture for ongoing maintenance. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/parameter-estimation-service.md | 344 ++++++--------------------- 1 file changed, 76 insertions(+), 268 deletions(-) diff --git a/docs/parameter-estimation-service.md b/docs/parameter-estimation-service.md index b619a57914..4b404dfb4d 100644 --- a/docs/parameter-estimation-service.md +++ b/docs/parameter-estimation-service.md @@ -1,4 +1,4 @@ -# Parameter Estimation Service Migration +# Parameter Estimation Service ## Requirements @@ -33,35 +33,7 @@ Parameter estimation allows VCell users to fit model parameters to experimental 11. **Shared filesystem for solver I/O.** The optimization problem input, progress report, and result output are files on a shared NFS mount — matching the Python solver's file-based interface. The database tracks metadata and status; the filesystem holds the data. -## Motivation - -Parameter estimation (curve fitting) is a core VCell capability that allows users to optimize model parameters against experimental data using the COPASI optimization engine. The current implementation has been unreliable in production (GitHub #1653), and the root cause is architectural: the REST-to-batch-server communication relies on persistent in-memory TCP socket connections that are inherently fragile. - -### Problems with the current design - -1. **In-memory socket map loses state on restart.** The legacy `vcell-api` stores active optimization connections in a `Hashtable` (`paramOptActiveSockets`). When the pod restarts — which is routine in Kubernetes — all running optimization jobs become unqueryable, producing `"Can't find connection for optimization ID=..."` errors. - -2. **No persistence.** There is no database record of optimization jobs. If the API process dies, all knowledge of submitted jobs is lost. Users see a failure with no way to recover results that may have already been computed and written to disk. - -3. **Custom TCP socket protocol.** The REST layer opens a raw TCP socket to `OptimizationBatchServer:8877` on the submit service, using Java object serialization for messages (`OptMessage.*`). This is fragile (connection drops, timeouts), difficult to debug, and a security concern (deserialization of untrusted objects). - -4. **DNS-based service discovery via `nslookup`.** The REST layer shells out to `nslookup` to resolve service hostnames. The code still references Docker swarm DNS naming conventions (`tasks.vcell{SITE}_submit`) from before the migration to Kubernetes, though it works in practice because the hostname is overridden via the `vcell.submit.service.host` property. This indirection is unnecessary — Kubernetes service DNS is the standard mechanism. - -5. **Collision-prone job IDs.** Job IDs are random integers in the range 0–999,999, generated with `new Random().nextInt(1000000)`. Collisions are possible. - -6. **Legacy API.** The optimization endpoints live on the legacy Restlet-based `vcell-api` (`/api/v0/`), which is being phased out in favor of the Quarkus-based `vcell-rest` (`/api/v1/`). The legacy API has no OpenAPI spec and no auto-generated clients. - -### What works well (and should not change) - -- **The Python solver** (`pythonCopasiOpt/vcell-opt`) is correct and well-tested. It reads an `OptProblem` JSON file, runs COPASI parameter estimation, writes results to an output JSON file, and writes intermediate progress (iteration count, objective function value, best parameters) to a TSV report file. This file-based interface is clean and should be preserved exactly as-is. - -- **SLURM submission** via `SlurmProxy.submitOptimizationJob()` works. The Singularity container, bind mounts, and SLURM configuration are all correct. - -- **The shared NFS filesystem** (`/simdata`) is already mounted on both `vcell-rest` and `vcell-submit` pods, and the Python solver writes results there. This is the natural communication channel for results. - -## Design - -### Architecture overview +## Architecture ``` Desktop Client / webapp @@ -86,7 +58,7 @@ vcell-submit (OpenWire JMS via ActiveMQConnectionFactory) | 3. Submit SLURM job via SlurmProxy.submitOptimizationJob() | 4. Publish OptStatusMessage to "opt-status" queue (QUEUED + htcJobId, or FAILED + error) v -SLURM → Singularity container → vcell-opt Python solver (UNCHANGED) +SLURM → Singularity container → vcell-opt Python solver | Writes to NFS: | - CopasiParest_{id}_optReport.txt (progress, written incrementally) | - CopasiParest_{id}_optRun.json (final results) @@ -111,9 +83,9 @@ vcell-rest (polling on client request) Client displays progress (objective function vs iteration graph, best parameter values) ``` -### Cross-protocol messaging through Artemis +## Cross-protocol messaging through Artemis -The optimization messaging uses **cross-protocol communication** through an Apache Artemis broker. This is a critical architectural detail that has been a source of bugs. +The optimization messaging uses **cross-protocol communication** through an Apache Artemis broker. **Protocol mapping:** - **vcell-rest** uses **AMQP 1.0** via Quarkus SmallRye Reactive Messaging (`quarkus-smallrye-reactive-messaging-amqp`) @@ -136,13 +108,13 @@ mp.messaging.incoming.subscriber-opt-status.address=opt-status mp.messaging.incoming.subscriber-opt-status.capabilities=queue ``` -**Lessons learned from deployment bugs:** +**Important configuration notes:** -1. **`address` is required.** Without explicit `address=opt-request`, SmallRye defaults to using the channel name (`publisher-opt-request`) as the AMQP address. The OpenWire consumer listens on queue `opt-request`, so messages are lost silently. The broker accepts and acknowledges the message (the AMQP disposition shows `Accepted`), but no consumer ever sees it. +1. **`address` is required.** Without explicit `address=opt-request`, SmallRye defaults to using the channel name (`publisher-opt-request`) as the AMQP address. The OpenWire consumer listens on queue `opt-request`, so messages are lost silently. -2. **`capabilities=queue` is required.** Artemis deploys queues as ANYCAST (point-to-point) by default, which is what OpenWire `session.createQueue()` creates. But SmallRye AMQP 1.0 consumers default to MULTICAST (pub-sub) semantics when attaching without specifying capabilities. This causes Artemis to create a separate MULTICAST subscription that never receives messages from the ANYCAST queue. The `capabilities=queue` annotation tells the AMQP client to attach with the "queue" capability, which Artemis interprets as ANYCAST. +2. **`capabilities=queue` is required.** Artemis deploys queues as ANYCAST by default. Without this, SmallRye AMQP creates a MULTICAST subscription that never receives messages from the ANYCAST queue. -3. **vcell-submit needs Artemis connection properties.** The `vcell.jms.artemis.host.internal` and `vcell.jms.artemis.port.internal` Java system properties must be set in the vcell-submit Dockerfile and K8s deployment. These are separate from the existing `activemqint` connection used for simulation job dispatch. +3. **vcell-submit needs Artemis connection properties.** `vcell.jms.artemis.host.internal` and `vcell.jms.artemis.port.internal` must be set in the vcell-submit container. These are separate from the existing `activemqint` connection used for simulation job dispatch. **K8s configuration** (in vcell-fluxcd `shared.env`): ``` @@ -150,55 +122,21 @@ jmshost_artemis_internal=artemismq jmsport_artemis_internal=61616 ``` -These are consumed by: -- vcell-rest: `application.properties` `%prod.amqp-host=${jmshost_artemis_internal}` (AMQP connection) -- vcell-submit: `Dockerfile-submit-dev` `-Dvcell.jms.artemis.host.internal="${jmshost_artemis_internal}"` (OpenWire connection) - -### Key design decisions - -**Database-backed job tracking.** Every optimization job gets a row in `vc_optjob`. The database is the source of truth for job lifecycle state (SUBMITTED → QUEUED → RUNNING → COMPLETE/FAILED/STOPPED). This survives pod restarts, supports multiple API replicas, and provides an audit trail. - -**Filesystem for data, database for state.** The OptProblem input, result output, and progress report are files on NFS — this matches the Python solver's file-based interface and avoids putting large blobs in the database. The database tracks job metadata and status; the filesystem holds the actual data. +## Key design decisions -**Artemis for job dispatch (cross-protocol).** vcell-rest sends a message to vcell-submit via Artemis to trigger SLURM submission, and vcell-submit sends a status message back. This replaces the persistent TCP socket with a durable message broker. The messages are small JSON payloads (job ID + file paths). The cross-protocol design (AMQP 1.0 ↔ OpenWire JMS) is necessary because vcell-rest uses Quarkus SmallRye AMQP while vcell-submit uses the ActiveMQ 5.x OpenWire client. +**Database-backed job tracking.** Every optimization job gets a row in `vc_optjob`. The database is the source of truth for job lifecycle state (SUBMITTED → QUEUED → RUNNING → COMPLETE/FAILED/STOPPED). This survives pod restarts and supports multiple API replicas. -**Database-sequence job IDs.** Replace the random 0–999,999 integer with `bigint` keys from the shared `newSeq` database sequence, consistent with every other VCell table (`vc_biomodel`, `vc_simulation`, etc.). Uses the existing `KeyValue` type and `KeyFactory.getNewKey()` infrastructure. +**Filesystem for data, database for state.** The OptProblem input, result output, and progress report are files on NFS — this matches the Python solver's file-based interface and avoids putting large blobs in the database. -**Progress reporting via filesystem polling.** The Python solver already writes a TSV report file incrementally as COPASI iterates. vcell-rest reads this file directly from NFS using `CopasiUtils.readProgressReportFromCSV()` (in `vcell-core`, already a dependency). This eliminates the batch server as a middleman for progress data. Each row contains: function evaluation count, best objective value, and best parameter vector. +**Database-sequence job IDs.** Bigint keys from the shared `newSeq` database sequence, consistent with every other VCell table. Uses the existing `KeyValue` type and `KeyFactory.getNewKey()`. **Filesystem-driven status promotion.** vcell-submit only sends a single `QUEUED` status message back after submitting the SLURM job. All subsequent status transitions are driven by vcell-rest reading the filesystem on each client poll: -- **SUBMITTED/QUEUED → RUNNING**: when the progress report file appears on NFS (meaning the SLURM job has started writing) -- **RUNNING → COMPLETE**: when the result output file appears on NFS (meaning the solver finished) +- **SUBMITTED/QUEUED → RUNNING**: when the progress report file appears on NFS +- **RUNNING → COMPLETE**: when the result output file appears on NFS -This design avoids the need for vcell-submit to monitor SLURM job state and send incremental status updates. The filesystem is the source of truth for solver progress, and the database tracks the lifecycle state. +**COPASI progress flushing.** The Python solver uses `basico.assign_report(..., confirm_overwrite=False)` to ensure COPASI flushes progress lines incrementally during execution. Without this, COPASI buffers the entire report until completion, preventing real-time progress updates. -**User-initiated stop via messaging.** When a user determines the optimization has sufficiently converged and stops the job, vcell-rest sends a stop message (with the SLURM job ID from the database) via Artemis to vcell-submit, which calls `killJobSafe()` → `scancel`. The report file retains all progress up to the kill point, so the client can read the best parameters found. - -### Real-time progress reporting - -The desktop client displays a real-time graph of objective function value vs. function evaluations, along with the current best parameter values. This updates every 2 seconds as the solver runs. - -**How it works:** - -1. The Python COPASI solver writes a TSV report file incrementally (`CopasiParest_{id}_optReport.txt`) with one row per sampled iteration. Format: - ``` - ["k1","k2"] ← header: JSON array of parameter names - 10 0.5 1.0 2.0 ← numEvals, objFuncValue, param1, param2, ... - 20 0.1 1.3 2.4 - 30 0.01 1.5 2.5 - ``` - -2. On each client poll, `OptimizationRestService.getOptimizationStatus()` reads this file via `CopasiUtils.readProgressReportFromCSV()`, which returns an `OptProgressReport` containing: - - `progressItems`: list of `OptProgressItem` (numFunctionEvaluations, objFuncValue) — one per sampled iteration - - `bestParamValues`: map of parameter name → best value found so far - -3. The client receives the `OptProgressReport` in the `OptimizationJobStatus` response and dispatches it to `CopasiOptSolverCallbacks.setProgressReport()`, which fires a `PropertyChangeEvent` to the UI. - -4. `RunStatusProgressDialog` (in `ParameterEstimationRunTaskPanel`) listens for these events and updates the objective function vs. evaluations plot and the best parameter table. - -**Key design choice:** Progress is read from the filesystem on every poll, not sent through the message queue. This means progress is available as soon as the SLURM job starts writing, even before the `QUEUED` status message arrives from vcell-submit. The server auto-promotes the status from `SUBMITTED`/`QUEUED` → `RUNNING` when it detects progress data on disk. This replicates the behavior of the legacy socket-based design where every status query re-read the report file. - -### Database schema +## Database schema ```sql CREATE TABLE vc_optjob ( @@ -215,19 +153,6 @@ CREATE TABLE vc_optjob ( ); ``` -| Column | Purpose | -|--------|---------| -| `id` | Database sequence key (bigint, from `newSeq`) | -| `ownerRef` | User who submitted (for access control) | -| `status` | SUBMITTED, QUEUED, RUNNING, COMPLETE, FAILED, STOPPED | -| `optProblemFile` | NFS path to input OptProblem JSON | -| `optOutputFile` | NFS path to result Vcellopt JSON | -| `optReportFile` | NFS path to progress report TSV | -| `htcJobId` | SLURM job ID (set when vcell-submit confirms submission) | -| `statusMessage` | Error description on failure, cancellation reason, etc. | -| `insertDate` | When the job was created | -| `updateDate` | Last status transition | - Status transitions: ``` SUBMITTED → QUEUED → RUNNING → COMPLETE @@ -235,7 +160,7 @@ SUBMITTED → QUEUED → RUNNING → COMPLETE → STOPPED (user-initiated) ``` -### REST API +## REST API ``` POST /api/v1/optimization Submit optimization job @@ -244,17 +169,7 @@ GET /api/v1/optimization/{id} Get job status, progress, or results POST /api/v1/optimization/{id}/stop Stop a running job ``` -**POST /api/v1/optimization** — Requires authenticated user. Accepts `OptProblem` JSON body. Writes input file to NFS, creates database record, publishes dispatch message to Artemis. Returns `OptimizationJobStatus` with the job ID and status=SUBMITTED. - -**GET /api/v1/optimization** — Requires authenticated user. Returns array of `OptimizationJobStatus` for the user's jobs, most recent first. Lightweight (no progress/results). - -**GET /api/v1/optimization/{id}** — Requires authenticated user (must be job owner). Returns `OptimizationJobStatus` which includes: -- `status` — current job state -- `progressReport` — (when RUNNING/QUEUED) iteration count, objective value, best parameters from the report file -- `results` — (when COMPLETE) the full `Vcellopt` result -- `statusMessage` — (when FAILED/STOPPED) error or cancellation description - -**POST /api/v1/optimization/{id}/stop** — Requires authenticated user (must be job owner). Sends stop message to vcell-submit via Artemis, updates database status to STOPPED. The client can then GET the job to read the last progress report for the best parameters found. +All endpoints require authentication (`@RolesAllowed("user")`) and enforce job ownership. ### Response DTO @@ -269,8 +184,6 @@ public record OptimizationJobStatus( ) {} ``` -The client checks `status` and reads the appropriate nullable field. This avoids the current string-prefix parsing pattern (`"QUEUED:"`, `"RUNNING:"`, etc.). - ### Message types Shared in `vcell-core` (`org.vcell.optimization` package), serialized as JSON: @@ -293,147 +206,52 @@ public class OptStatusMessage { } ``` -## Desktop client architecture (current) +## Progress reporting -The desktop client has a layered architecture for parameter estimation: +The desktop client displays a real-time graph of objective function value vs. function evaluations, along with the current best parameter values. + +1. The Python COPASI solver writes a TSV report file incrementally (`CopasiParest_{id}_optReport.txt`): + ``` + ["k1","k2"] ← header: JSON array of parameter names + 10 0.5 1.0 2.0 ← numEvals, objFuncValue, param1, param2, ... + 20 0.1 1.3 2.4 + 30 0.01 1.5 2.5 + ``` + +2. On each client poll, `OptimizationRestService.getOptimizationStatus()` reads this file via `CopasiUtils.readProgressReportFromCSV()`, returning an `OptProgressReport` with `progressItems` and `bestParamValues`. + +3. The client dispatches progress to `CopasiOptSolverCallbacks.setProgressReport()` via `SwingUtilities.invokeLater`, which fires a `PropertyChangeEvent` to the `RunStatusProgressDialog`. + +## Desktop client architecture ### UI layer -- **`ParameterEstimationPanel`** (`vcell-client/.../biomodel/ParameterEstimationPanel.java`) — Container panel with tabs for data, parameters, and run configuration. -- **`ParameterEstimationRunTaskPanel`** (`vcell-client/.../optimization/gui/ParameterEstimationRunTaskPanel.java`) — Main run panel. The `solve()` method (line 1126) dispatches an async task chain that calls `CopasiOptimizationSolverRemote.solveRemoteApi()`. -- **`RunStatusProgressDialog`** (inner class of `ParameterEstimationRunTaskPanel`, line 101) — Modal dialog showing real-time progress: number of evaluations, objective function value, and a log10(error) vs evaluations plot. - -### Solver coordination layer - -- **`CopasiOptimizationSolverRemote`** (`vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java`) — Orchestrates the remote optimization call. The `solveRemoteApi()` method: - 1. Converts `ParameterEstimationTask` → `OptProblem` via `CopasiUtils.paramTaskToOptProblem()` - 2. Converts to generated client model type and calls `optApi.submitOptimization(optProblem)` - 3. Polls `optApi.getOptimizationStatus(jobId)` every 2 seconds with a 200-second timeout - 4. Uses typed `OptimizationJobStatus` fields (`status`, `progressReport`, `results`) instead of string-prefix parsing - 5. Updates `CopasiOptSolverCallbacks` with progress via a pluggable `progressDispatcher` (SwingUtilities::invokeLater in GUI, Runnable::run in tests) - 6. Handles user stop via `optApi.stopOptimization(jobId)` - -### Callback / event layer - -- **`CopasiOptSolverCallbacks`** (`vcell-core/.../optimization/CopasiOptSolverCallbacks.java`) — Bridges the solver and the UI via `PropertyChangeListener`. Carries `OptProgressReport` (progress data) and `stopRequested` (user stop signal). - -### API client layer - -- **`OptimizationResourceApi`** (auto-generated in `vcell-restclient`) — Typed REST client for `/api/v1/optimization` endpoints. Generated from the OpenAPI spec via `tools/openapi-clients.sh`. - -- **`VCellApiClient`** (`vcell-apiclient/.../api/client/VCellApiClient.java`) — Provides access to the generated `OptimizationResourceApi` via `getOptimizationApi()`. - -## Implementation status - -### Completed (parest-bug branch) - -All commits listed below are on the `parest-bug` branch, ahead of `master`. - -#### Commit 1: Database schema and service layer -- `OptJobTable` in `vcell-core` with Oracle-compatible SQL generation -- `OptimizationRestService` (`@ApplicationScoped`) with database CRUD and filesystem read methods -- `OptimizationJobStatus` record DTO, `OptJobRecord`, `OptJobStatus` enum -- Bigint job IDs from database sequence via `KeyFactory.getNewKey()` - -#### Commit 2: REST endpoints -- `OptimizationResource.java` — submit, status, list, stop endpoints -- Authentication via `@RolesAllowed("user")`, ownership checks via user ID -- `POST /api/v1/optimization`, `GET /api/v1/optimization/{id}`, `GET /api/v1/optimization`, `POST /api/v1/optimization/{id}/stop` -- CodeQL path traversal fixes for optimization file paths - -#### Commit 3: ActiveMQ messaging (vcell-rest side) -- `OptimizationMQ.java` — SmallRye AMQP producer (`publisher-opt-request`) and consumer (`subscriber-opt-status`) -- `OptRequestMessage` and `OptStatusMessage` shared types in `vcell-core` -- AMQP channel config in `application.properties` with `capabilities=queue` for ANYCAST routing -- Artemis broker connection: `%prod.amqp-host`, `%prod.amqp-port` - -#### Commit 4: ActiveMQ messaging (vcell-submit side) -- `OptimizationBatchServer.initOptimizationQueue()` — OpenWire JMS listener on `opt-request` queue -- `handleSubmitRequest()` — reads OptProblem from NFS, submits to SLURM, sends QUEUED status back on `opt-status` -- `handleStopRequest()` — kills SLURM job via `HtcProxy.killJobSafe()` -- `sendStatusMessage()` — sends `OptStatusMessage` JSON on `opt-status` queue -- Path validation via `validateParestPath()` to prevent traversal -- Artemis connection configured via `vcell.jms.artemis.host.internal` / `vcell.jms.artemis.port.internal` system properties -- `Dockerfile-submit-dev` updated with Artemis env vars and `-D` flags - -#### Commit 5: Tests - -**Level 1: REST + DB + filesystem** (`OptimizationResourceTest.java`, `@Tag("Quarkus")`) -- 10 tests covering submit, status polling, completion detection, stop, authorization, error handling -- Uses testcontainers for PostgreSQL and Keycloak -- Simulates solver by writing mock report/output files to temp directory - -**Level 1.5: E2E client flow** (`OptimizationE2ETest.java`, `@Tag("Quarkus")`) -- Tests the same code path as `CopasiOptimizationSolverRemote.solveRemoteApi()` -- Uses the generated `OptimizationResourceApi` client -- Mock vcell-submit directly updates database (bypasses messaging) -- Tests submit+poll+complete and submit+stop flows - -**Level 2: Cross-protocol messaging** (`OptimizationCrossProtocolTest.java`, `@Tag("Quarkus")`) -- Full AMQP 1.0 ↔ OpenWire JMS round-trip through a real Artemis testcontainer -- `ArtemisTestResource` — `QuarkusTestResourceLifecycleManager` that starts Artemis with both AMQP (5672) and OpenWire (61616) ports -- `OpenWireOptSubmitStub` — mirrors `OptimizationBatchServer.handleSubmitRequest()` using `ActiveMQConnectionFactory` (same OpenWire protocol as production vcell-submit) -- Validates: address mapping, ANYCAST/MULTICAST routing, JSON serialization across protocols, filesystem handoff (stub reads OptProblem file written by vcell-rest) -- This test would have caught both production bugs (wrong AMQP address and MULTICAST subscription) - -#### Commit 6: OpenAPI client regeneration -- Consolidated `tools/generate.sh` + `tools/compile-and-build-clients.sh` into `tools/openapi-clients.sh` -- Regenerated Java (`vcell-restclient`), Python (`python-restclient`), TypeScript (`webapp-ng`) clients -- New `OptimizationResourceApi` class with `submitOptimization()`, `getOptimizationStatus()`, `stopOptimization()`, `listOptimizationJobs()` - -#### Commit 7: Desktop client update -- `CopasiOptimizationSolverRemote.solveRemoteApi()` rewritten to use generated `OptimizationResourceApi` -- Typed `OptimizationJobStatus` fields replace string-prefix parsing -- `POST /{id}/stop` replaces `bStop` query parameter -- Testable overload accepts `OptimizationResourceApi` and `Consumer` dispatcher directly (no Swing dependency) -- 200-second timeout, 2-second poll interval - -#### Deployment fixes (discovered during dev deployment) -- AMQP address mapping: added `address=opt-request` / `address=opt-status` to production channel config (without this, SmallRye sent to channel name instead of queue name) -- ANYCAST routing: added `capabilities=queue` to all AMQP channel configs (without this, SmallRye created MULTICAST subscriptions that missed OpenWire messages) -- Artemis connection for vcell-submit: added `vcell.jms.artemis.host.internal` / `vcell.jms.artemis.port.internal` to `Dockerfile-submit-dev` -- JDBC resource leak: wrapped Statement/ResultSet in try-with-resources in `getOptJobRecord()` and `listOptimizationJobs()` - -#### Progress reporting fix -- Server: `SUBMITTED` status now reads the progress report file (the SLURM job may already be running before the async QUEUED message arrives). Auto-promotes SUBMITTED/QUEUED → RUNNING when progress data appears on disk. -- Client: `SUBMITTED`/`QUEUED`/`RUNNING` all dispatch progress to `CopasiOptSolverCallbacks.setProgressReport()` when available, so the objective function graph and best parameter values update as soon as the solver starts writing. - -### Remaining work - -#### Deploy and validate on dev -- Deploy latest release to dev (vcell-dev.cam.uchc.edu) -- Test full round-trip: desktop client → vcell-rest → Artemis → vcell-submit → SLURM → NFS → vcell-rest → client -- Verify progress reporting works (client sees iteration updates) -- Verify stop works (SLURM job is cancelled, client gets last progress) -- Monitor for JDBC leak warnings (should be gone) - -#### Commit 8: Remove legacy optimization code (deferred until new path validated) -- Delete `OptimizationRunServerResource.java` from `vcell-api` -- Delete `OptimizationRunResource.java` (interface) from `vcell-api` -- Delete `OptMessage.java` from `vcell-core` -- Remove socket server from `OptimizationBatchServer` (`initOptimizationSocket()`, `OptCommunicationThread`) -- Remove socket initialization from `HtcSimulationWorker.init()` -- Remove `submitOptimization()` / `getOptRunJson()` from `VCellApiClient` -- Remove optimization route registration from `VCellApiApplication.java` -- Remove port 8877 exposure from vcell-submit - -#### Update design documentation after legacy removal -- After commit 8, remove the "Motivation" section's references to the legacy design (socket protocol, random IDs, etc.) — they will no longer be relevant context -- Remove the "Decommissioning" section (will be complete) -- Update architecture diagram to remove references to legacy `/api/v0/` endpoints -- Simplify the document to be a pure design reference rather than a migration plan - -#### Level 3 integration test (future) -- Full end-to-end test with SLURM (`@Tag("SLURM_IT")`) — requires NFS and SSH access to SLURM cluster -- Not run in CI; skipped if required system properties are missing -- Submits a small, fast-converging OptProblem with low max iterations -- Verifies real progress reports and optimized parameter values - -#### Future improvements -- Consider migrating vcell-submit from ActiveMQ 5.x OpenWire client to Artemis JMS client (`jakarta.jms`) for protocol consistency -- Add dead letter and expiry address configuration for opt-request/opt-status queues in Artemis -- Add monitoring/alerting for optimization job failures -- Consider increasing the 200-second client timeout or making it configurable +- **`ParameterEstimationRunTaskPanel`** — Main run panel. `solve()` dispatches an async task chain calling `CopasiOptimizationSolverRemote.solveRemoteApi()`. +- **`RunStatusProgressDialog`** (inner class) — Modal dialog showing evaluations, objective value, and log10(error) vs evaluations plot. + +### Solver coordination + +- **`CopasiOptimizationSolverRemote`** — Orchestrates the remote call: converts `ParameterEstimationTask` → `OptProblem`, submits via generated API client, polls every 2 seconds (200-second timeout), dispatches progress to callbacks, handles stop. + +### Callback layer + +- **`CopasiOptSolverCallbacks`** — Bridges solver and UI via `PropertyChangeListener`. Carries `OptProgressReport` and `stopRequested`. + +### API client + +- **`OptimizationResourceApi`** (auto-generated in `vcell-restclient`) — Typed REST client generated from the OpenAPI spec via `tools/openapi-clients.sh`. +- **`VCellApiClient.getOptimizationApi()`** — Factory method providing access to the generated client. + +## Python solver (vcell-opt) + +The COPASI parameter estimation solver runs as a Singularity container on the SLURM cluster. + +- **Location:** `pythonCopasiOpt/vcell-opt/` +- **Dependencies:** `copasi-basico ^0.86`, `python-copasi ^4.45.298`, Python `^3.10` +- **Docker image:** `ghcr.io/virtualcell/vcell-opt:` (Dockerfile at `pythonCopasiOpt/Dockerfile`, Debian bookworm base) +- **Entry point:** `vcell_opt.optService.run_command(opt_file, result_file, report_file)` + +The solver reads an `OptProblem` JSON, runs COPASI parameter estimation, writes results to `_optRun.json`, and writes incremental progress to `_optReport.txt`. ## Key files @@ -450,33 +268,23 @@ All commits listed below are on the `parest-bug` branch, ahead of `master`. | `vcell-core/.../optimization/OptJobStatus.java` | Status enum | | `vcell-core/.../modeldb/OptJobTable.java` | Database table definition | | `vcell-client/.../copasi/CopasiOptimizationSolverRemote.java` | Desktop client solver | -| `docker/build/Dockerfile-submit-dev` | vcell-submit container with Artemis config | +| `pythonCopasiOpt/vcell-opt/vcell_opt/optService.py` | Python COPASI solver | | `tools/openapi-clients.sh` | OpenAPI client generation script | -## Decommissioning the legacy `/api/v0/optimization` endpoints - -The legacy endpoints must remain available during a transition period because deployed desktop clients (already installed on user machines) will continue to call `/api/v0/optimization` until they update. The decommissioning plan: +## Tests -### Phase 1: Parallel operation (CURRENT) +| Test | Level | What it covers | +|------|-------|----------------| +| `OptimizationResourceTest` (`@Tag("Quarkus")`) | REST + DB + filesystem | Submit, poll, completion, stop, authorization, errors. Testcontainers for PostgreSQL + Keycloak. | +| `OptimizationE2ETest` (`@Tag("Quarkus")`) | Client flow | Same code path as `CopasiOptimizationSolverRemote.solveRemoteApi()` using generated API client. | +| `OptimizationCrossProtocolTest` (`@Tag("Quarkus")`) | Cross-protocol messaging | Full AMQP 1.0 ↔ OpenWire JMS round-trip through real Artemis testcontainer with OpenWire stub. | +| `SlurmProxyTest` (`@Tag("Fast")`) | SLURM script generation | Verifies optimization SLURM job script matches expected fixture. | +| `vcellopt_test.py::test_incremental_report_writing` | Solver progress flushing | Multiprocessing test verifying COPASI flushes report file incrementally. | -Both old and new endpoints are live. The new `/api/v1/optimization` endpoints are deployed and being validated on dev. The old `/api/v0/optimization` endpoints continue to work as before (same socket-based implementation, same bugs). The desktop client on the `parest-bug` branch uses the new API. +## Future improvements -### Phase 2: Client migration (after dev validation) - -Merge `parest-bug` to `master` and release. New client builds use the new API exclusively. Old client installs still use `/api/v0/`. - -VCell uses a managed client update mechanism — when users launch the desktop client, it checks for updates and prompts to download the latest version. This means the transition window depends on how quickly users update. - -### Phase 3: Deprecation monitoring - -After the updated client is released: -- Add logging to the legacy `/api/v0/optimization` endpoints to track usage -- Monitor for a period (e.g., 2–4 weeks) to see if any clients are still hitting the old endpoints -- Communicate the deprecation via the VCell user mailing list if needed - -### Phase 4: Removal (commit 8) - -Once legacy endpoint usage drops to zero (or an acceptable threshold): -- Remove the legacy optimization endpoints, socket server, and related code -- Remove port 8877 from the vcell-submit Kubernetes service -- The `VCellApiClient` class itself is not deleted (it may still be used for other legacy endpoints), but its optimization methods are removed +- Migrate vcell-submit from ActiveMQ 5.x OpenWire client to Artemis JMS client (`jakarta.jms`) for protocol consistency +- Add dead letter and expiry address configuration for opt-request/opt-status queues in Artemis +- Add monitoring/alerting for optimization job failures +- Increase the 200-second client timeout or make it configurable +- Add Level 3 SLURM integration test (`@Tag("SLURM_IT")`) for full end-to-end validation with real SLURM From f5b526b81764e389cfe32b81034d15d928bcbdd6 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Sat, 11 Apr 2026 13:55:16 -0400 Subject: [PATCH 36/40] Increase optimization polling timeout from 200 seconds to 10 minutes Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/parameter-estimation-service.md | 4 ++-- .../src/main/java/copasi/CopasiOptimizationSolverRemote.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/parameter-estimation-service.md b/docs/parameter-estimation-service.md index 4b404dfb4d..946c9d2a73 100644 --- a/docs/parameter-estimation-service.md +++ b/docs/parameter-estimation-service.md @@ -231,7 +231,7 @@ The desktop client displays a real-time graph of objective function value vs. fu ### Solver coordination -- **`CopasiOptimizationSolverRemote`** — Orchestrates the remote call: converts `ParameterEstimationTask` → `OptProblem`, submits via generated API client, polls every 2 seconds (200-second timeout), dispatches progress to callbacks, handles stop. +- **`CopasiOptimizationSolverRemote`** — Orchestrates the remote call: converts `ParameterEstimationTask` → `OptProblem`, submits via generated API client, polls every 2 seconds (10-minute timeout), dispatches progress to callbacks, handles stop. ### Callback layer @@ -286,5 +286,5 @@ The solver reads an `OptProblem` JSON, runs COPASI parameter estimation, writes - Migrate vcell-submit from ActiveMQ 5.x OpenWire client to Artemis JMS client (`jakarta.jms`) for protocol consistency - Add dead letter and expiry address configuration for opt-request/opt-status queues in Artemis - Add monitoring/alerting for optimization job failures -- Increase the 200-second client timeout or make it configurable +- Make the 10-minute client timeout configurable - Add Level 3 SLURM integration test (`@Tag("SLURM_IT")`) for full end-to-end validation with real SLURM diff --git a/vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java b/vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java index 72fddaf88c..25d9bda226 100644 --- a/vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java +++ b/vcell-client/src/main/java/copasi/CopasiOptimizationSolverRemote.java @@ -80,7 +80,7 @@ public static OptimizationResultSet solveRemoteApi( lg.info("submitted optimization jobID={}", jobId); // Poll for status and results - final long TIMEOUT_MS = 1000 * 200; // 200 second timeout + final long TIMEOUT_MS = 1000 * 600; // 10 minute timeout long startTime = System.currentTimeMillis(); if (clientTaskStatusSupport != null) { clientTaskStatusSupport.setMessage("Waiting for progress..."); From 58a59f12fdd483f3f0c88e6a0cd36404ab00630c Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Sat, 11 Apr 2026 23:12:13 -0400 Subject: [PATCH 37/40] Fix CodeQL path traversal warnings in SlurmProxy and test stub - SlurmProxy.submitOptimizationJob: validate sub_file_internal is under htcLogDir and use canonical path for writeString - OpenWireOptSubmitStub: add validatePath() and use canonical paths for all file operations (matches real OptimizationBatchServer.validateParestPath) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../testresources/OpenWireOptSubmitStub.java | 31 +++++++++++++------ .../message/server/htc/slurm/SlurmProxy.java | 7 ++++- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/vcell-rest/src/test/java/org/vcell/restq/testresources/OpenWireOptSubmitStub.java b/vcell-rest/src/test/java/org/vcell/restq/testresources/OpenWireOptSubmitStub.java index 5ddc9e9f7c..2102d2d1d7 100644 --- a/vcell-rest/src/test/java/org/vcell/restq/testresources/OpenWireOptSubmitStub.java +++ b/vcell-rest/src/test/java/org/vcell/restq/testresources/OpenWireOptSubmitStub.java @@ -103,21 +103,34 @@ public void start() throws Exception { * Mirrors OptimizationBatchServer.handleSubmitRequest() — same validation, same file I/O, * same status message pattern. Only SLURM submission is replaced with direct file writes. */ + /** + * Validate that a file path is under the expected parest_data directory to prevent path traversal. + * Same logic as OptimizationBatchServer.validateParestPath(). + */ + private static File validatePath(String filePath, String baseDir) throws java.io.IOException { + File file = new File(filePath).getCanonicalFile(); + if (!file.getPath().startsWith(new File(baseDir).getCanonicalPath())) { + throw new java.io.IOException("Invalid file path (outside base dir): " + filePath); + } + return file; + } + private void handleSubmitRequest(OptRequestMessage request, Session session, MessageProducer producer) { try { // Validate jobId is numeric (same as real handleSubmitRequest) Long.parseLong(request.jobId); - // Read the OptProblem file from the path in the message - // (validates that vcell-rest wrote it to the correct location) - File optProblemFile = new File(request.optProblemFilePath); + // Validate and read the OptProblem file from the path in the message + File optProblemFile = validatePath(request.optProblemFilePath, + new File(request.optProblemFilePath).getParentFile().getCanonicalPath()); OptProblem optProblem = objectMapper.readValue(optProblemFile, OptProblem.class); lg.info("Stub: read OptProblem from {}, params={}", optProblemFile.getName(), optProblem.getParameterDescriptionList().size()); - // Use file paths from the message (same as real code uses request.optOutputFilePath, etc.) - File optOutputFile = new File(request.optOutputFilePath); - File optReportFile = new File(request.optReportFilePath); + // Validate file paths from the message + String baseDir = optProblemFile.getParentFile().getCanonicalPath(); + File optOutputFile = validatePath(request.optOutputFilePath, baseDir); + File optReportFile = validatePath(request.optReportFilePath, baseDir); // Write progress report file (simulates what the SLURM CopasiParest job writes) writeProgressReport(optReportFile); @@ -142,8 +155,8 @@ private void handleSubmitRequest(OptRequestMessage request, Session session, Mes } private void writeProgressReport(File reportFile) throws Exception { - reportFile.getParentFile().mkdirs(); - java.nio.file.Files.writeString(reportFile.toPath(), + reportFile.getCanonicalFile().getParentFile().mkdirs(); + java.nio.file.Files.writeString(reportFile.getCanonicalFile().toPath(), "[\"k1\",\"k2\"]\n" + "10\t0.5\t1.0\t2.0\n" + "20\t0.1\t1.3\t2.4\n" + @@ -172,7 +185,7 @@ private void writeResults(File outputFile) throws Exception { progressReport.setProgressItems(List.of(item1, item2, item3)); resultSet.setOptProgressReport(progressReport); vcellopt.setOptResultSet(resultSet); - objectMapper.writeValue(outputFile, vcellopt); + objectMapper.writeValue(outputFile.getCanonicalFile(), vcellopt); } /** diff --git a/vcell-server/src/main/java/cbit/vcell/message/server/htc/slurm/SlurmProxy.java b/vcell-server/src/main/java/cbit/vcell/message/server/htc/slurm/SlurmProxy.java index 98f1686b87..91a205bc68 100644 --- a/vcell-server/src/main/java/cbit/vcell/message/server/htc/slurm/SlurmProxy.java +++ b/vcell-server/src/main/java/cbit/vcell/message/server/htc/slurm/SlurmProxy.java @@ -1084,13 +1084,18 @@ HtcJobID submitJobFile(File sub_file_external) throws ExecutableException { public HtcJobID submitOptimizationJob(String jobName, File sub_file_internal, File sub_file_external, File optProblemInputFile,File optProblemOutputFile,File optReportFile) throws ExecutableException{ try { + // Validate sub_file_internal is under the configured htcLogDir to prevent path traversal + String htcLogDirInternal = PropertyLoader.getRequiredProperty(PropertyLoader.htcLogDirInternal); + if (!sub_file_internal.getCanonicalPath().startsWith(new File(htcLogDirInternal).getCanonicalPath())) { + throw new ExecutableException("sub_file_internal path outside htcLogDir: " + sub_file_internal); + } String scriptText = createOptJobScript(jobName, optProblemInputFile, optProblemOutputFile, optReportFile); LG.info("sub_file_internal: " + sub_file_internal.getAbsolutePath() + ", sub_file_external: " + sub_file_external.getAbsolutePath() + ", optProblemInput: " + optProblemInputFile.getAbsolutePath() + ", optProblemOutput: " + optProblemOutputFile.getAbsolutePath() + ", optReport: " + optReportFile.getAbsolutePath()); - Files.writeString(sub_file_internal.toPath(), scriptText); + Files.writeString(sub_file_internal.getCanonicalFile().toPath(), scriptText); } catch (IOException ex) { LG.error(ex); return null; From 496547ecbafb7813a42b39b0b5ec776271fbdbaa Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Wed, 15 Apr 2026 07:22:48 -0400 Subject: [PATCH 38/40] Fix CodeQL partial path traversal alert #226 Using getCanonicalPath().startsWith(String) is not slash-terminated, so /data/parest_data would incorrectly match /data/parest_data_evil. Switch to Path.startsWith(Path) which compares path segments correctly. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../vcell/restq/testresources/OpenWireOptSubmitStub.java | 3 ++- .../message/server/batch/opt/OptimizationBatchServer.java | 6 +++--- .../cbit/vcell/message/server/htc/slurm/SlurmProxy.java | 4 +++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/vcell-rest/src/test/java/org/vcell/restq/testresources/OpenWireOptSubmitStub.java b/vcell-rest/src/test/java/org/vcell/restq/testresources/OpenWireOptSubmitStub.java index 2102d2d1d7..78cd2c3a87 100644 --- a/vcell-rest/src/test/java/org/vcell/restq/testresources/OpenWireOptSubmitStub.java +++ b/vcell-rest/src/test/java/org/vcell/restq/testresources/OpenWireOptSubmitStub.java @@ -109,7 +109,8 @@ public void start() throws Exception { */ private static File validatePath(String filePath, String baseDir) throws java.io.IOException { File file = new File(filePath).getCanonicalFile(); - if (!file.getPath().startsWith(new File(baseDir).getCanonicalPath())) { + java.nio.file.Path basePath = new File(baseDir).getCanonicalFile().toPath(); + if (!file.toPath().startsWith(basePath)) { throw new java.io.IOException("Invalid file path (outside base dir): " + filePath); } return file; diff --git a/vcell-server/src/main/java/cbit/vcell/message/server/batch/opt/OptimizationBatchServer.java b/vcell-server/src/main/java/cbit/vcell/message/server/batch/opt/OptimizationBatchServer.java index f0538e5cf3..ac12fb0704 100644 --- a/vcell-server/src/main/java/cbit/vcell/message/server/batch/opt/OptimizationBatchServer.java +++ b/vcell-server/src/main/java/cbit/vcell/message/server/batch/opt/OptimizationBatchServer.java @@ -89,9 +89,9 @@ public void initOptimizationQueue(String jmsHost, int jmsPort) { */ private static File validateParestPath(String filePath) throws IOException { File file = new File(filePath).getCanonicalFile(); - String parestDir = new File(PropertyLoader.getRequiredProperty( - PropertyLoader.primarySimDataDirInternalProperty), "parest_data").getCanonicalPath(); - if (!file.getPath().startsWith(parestDir)) { + java.nio.file.Path parestDir = new File(PropertyLoader.getRequiredProperty( + PropertyLoader.primarySimDataDirInternalProperty), "parest_data").getCanonicalFile().toPath(); + if (!file.toPath().startsWith(parestDir)) { throw new IOException("Invalid optimization file path (outside parest_data): " + filePath); } return file; diff --git a/vcell-server/src/main/java/cbit/vcell/message/server/htc/slurm/SlurmProxy.java b/vcell-server/src/main/java/cbit/vcell/message/server/htc/slurm/SlurmProxy.java index 91a205bc68..b4bea68fa2 100644 --- a/vcell-server/src/main/java/cbit/vcell/message/server/htc/slurm/SlurmProxy.java +++ b/vcell-server/src/main/java/cbit/vcell/message/server/htc/slurm/SlurmProxy.java @@ -1086,7 +1086,9 @@ public HtcJobID submitOptimizationJob(String jobName, File sub_file_internal, Fi try { // Validate sub_file_internal is under the configured htcLogDir to prevent path traversal String htcLogDirInternal = PropertyLoader.getRequiredProperty(PropertyLoader.htcLogDirInternal); - if (!sub_file_internal.getCanonicalPath().startsWith(new File(htcLogDirInternal).getCanonicalPath())) { + java.nio.file.Path subFilePath = sub_file_internal.getCanonicalFile().toPath(); + java.nio.file.Path htcLogDirPath = new File(htcLogDirInternal).getCanonicalFile().toPath(); + if (!subFilePath.startsWith(htcLogDirPath)) { throw new ExecutableException("sub_file_internal path outside htcLogDir: " + sub_file_internal); } String scriptText = createOptJobScript(jobName, optProblemInputFile, optProblemOutputFile, optReportFile); From 7d68604f1c8861782e1f64d2de5fad063f6140d2 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Wed, 15 Apr 2026 08:38:57 -0400 Subject: [PATCH 39/40] Fail fast when notarytool submit fails Previously, if 'notarytool submit' failed (e.g., 403 agreement error), the script continued and spent 5 minutes polling notarytool info with an empty UUID, producing misleading 'Missing expected argument' errors. Now check submit exit status and UUID non-empty before polling. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/site_deploy.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/site_deploy.yml b/.github/workflows/site_deploy.yml index 9087039b35..ff5ef15b7b 100644 --- a/.github/workflows/site_deploy.yml +++ b/.github/workflows/site_deploy.yml @@ -116,9 +116,15 @@ jobs: cd installers export MAC_INSTALLER=`ls *dmg` xcrun notarytool submit --output-format normal --no-progress --no-wait --team-id "${{ secrets.MACTEAMID }}" --apple-id "${{ secrets.MACID }}" --password "${{ secrets.MACPW }}" $MAC_INSTALLER > submit_output + SUBMIT_EXIT=$? echo "output returned by notarytool submit:" cat submit_output - cat submit_output | grep "id:" | cut -d ':' -f2 > UUID + cat submit_output | grep "id:" | cut -d ':' -f2 | tr -d '[:space:]' > UUID + if [[ $SUBMIT_EXIT != 0 || ! -s UUID ]]; then + echo "notarytool submit failed (exit=$SUBMIT_EXIT), no submission UUID obtained - aborting" + exit 1 + fi + echo "submission UUID: $(cat UUID)" for minutes in {1..5} do sleep 60 From e57a07c2e7b0fccd7e8be3f0725e6efe292db7ce Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Wed, 15 Apr 2026 15:59:11 -0400 Subject: [PATCH 40/40] Restore executable permission on langevin_x64 in batch container actions/upload-artifact@v4 does not preserve POSIX permissions, so langevin_x64 arrives at the docker-build job as mode 644. The other /vcellbin/ solver binaries are executable because they come from the vcell-solvers base image; only langevin_x64 is COPY'd from the uploaded maven-build-output artifact. Use COPY --chmod=755 to set the exec bit at image-build time. Co-Authored-By: Claude Opus 4.6 (1M context) --- docker/build/Dockerfile-batch-dev | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/build/Dockerfile-batch-dev b/docker/build/Dockerfile-batch-dev index 4a5d79cda8..202d3eabde 100644 --- a/docker/build/Dockerfile-batch-dev +++ b/docker/build/Dockerfile-batch-dev @@ -17,7 +17,7 @@ COPY ./vcell-server/target/vcell-server-0.0.1-SNAPSHOT.jar \ ./lib/ COPY ./nativelibs/linux64 ./nativelibs/linux64 -COPY ./localsolvers/linux64/langevin_x64 /vcellbin/ +COPY --chmod=755 ./localsolvers/linux64/langevin_x64 /vcellbin/ COPY ./docker/build/batch/JavaPreprocessor64 \ ./docker/build/batch/JavaPostprocessor64 \ ./docker/build/batch/JavaSimExe64 \