Skip to main content

PV and Battery Simulations

The opt-in hybrid engine simulates PV, standalone storage, or a combined plant with a single chronological battery and dispatch execution layer. Submit the hybrid-1 input contract to POST /v2/energymodels?calculationEngine=hybrid. The service must have hybrid admission enabled; otherwise submissions return 503. Existing PV submissions keep their default engine and request contract. Hybrid inputs require the explicit selector on both create and saved rerun. The selector does not convert an existing PV-only saved model into a hybrid model. For an existing solar template, use the explicit solar preparation flow to build and review a native solar-only request before submission. Use the /v2 base path for hybrid authentication, equipment reads, task polling, results, saved updates and reruns. The original API remains available for existing solar models. The versioned API initially requires the hybrid selector for simulations, including PV-only studies; it returns 409 for historical solar mutations. Historical detail reads redirect to the original endpoint after authentication and workspace checks, preserving the stored model’s original input and output contract.

Choose an execution mode

For physical networks, options.electrical_model can explicitly select reduced, audit, or full. The separate collection_equivalent option is restricted to solar-template screening with supported collection loss laws and controls; it does not provide a physical topology or battery model. dispatch.method="optimized" can be combined with a detailed electrical mode. The audit mode reports independent checks; it does not replace the reduced trajectory with the audit result. Without explicit audit_interval_indices, it retains first/last points, electrical extrema, and a bounded set of topology states, with at most 28 snapshots. Explicit indices retain accepted physical subintervals within the selected intervals and remain subject to the output budget. The default hourly solar-only path has a tens-of-seconds execution requirement; validate its performance separately from BESS and optional detailed calculations. BESS runtime depends on interval count, equipment count, state-boundary events, and numerical iterations. Optimization and full electrical runs use the long-running compute path and can take substantially longer. Larger fast and audit studies also use that path: routing counts intervals times the largest equipment, PV-group, or AC-network count and switches above 200,000. This is a scheduling threshold, not a runtime or accuracy guarantee. A client polling timeout does not cancel a calculation.

Define physical connections and units

Every asset ID is supplied by the caller and must be unique across the plant. References identify actual buses and converters, rather than accounting groups. Scalar time-series fields repeat over the entire study. Array fields must match the time axis. JSON inputs accept the modeled snake_case names and their camelCase aliases, for example duration_seconds / durationSeconds and capacity_mwh / capacityMwh. Continue using camelCase if desired. Send each property once; do not submit duplicate aliases. Query parameters such as calculationEngine retain their documented spelling. Arbitrary asset IDs, dictionary keys, and saved input JSON are not recursively renamed. For AC coupling, storage has its own bidirectional PCS and DC bus, separate from the PV inverter. For controlled DC coupling, PV and storage reference the same converter and DC bus, and the battery uses connection="controlled_dc_dc". The physical converter’s active-power, apparent-power, current, and voltage limits apply once to its combined operating point. DC/DC, cell, converter, auxiliary, and network losses have separate ownership.

Synthetic AC-coupled example

Save this as hybrid-ac.json. The four hourly intervals deliberately use ideal equipment and a single AC bus to illustrate boundaries. These assumptions are not manufacturer equipment data. No project or location is required when PV availability is supplied explicitly.
available_ac_mw is availability at the inverter AC output, before collector and POI losses. Its inaccessible upstream DC power cannot be used to charge a DC-coupled battery. This example charges 1 MWh and discharges 1 MWh, returns to 0.5 MWh stored inventory, and exports 3 MWh over the four hours.

Synthetic DC-coupled example

This complete transformation of the previous example creates hybrid-dc.json. It puts PV and storage behind one 1 MW shared inverter and exposes PV availability at its DC bus. The first two intervals can charge storage with the 0.5 MW that would otherwise exceed the shared inverter’s capacity.

Submit, poll, edit, and rerun

Submit either JSON file using your usual authenticated workspace credentials:
A successful submission returns 202 with energyModelId, taskDetails.taskId, calculationEngine, calculationRunId, contractVersion, and preset. Standalone models omit project and location identity fields.
  1. Poll GET /tasks/{taskId} until a terminal status.
  2. Read GET /energymodels/{energyModelId}?include=inputs for the saved request, normalized request, and raw hybrid outputs.
  3. Save an edited request with PUT /energymodels/{energyModelId}. This resets the saved result and leaves the row in draft status.
  4. Queue it again with POST /energymodels/{energyModelId}/run?calculationEngine=hybrid.
Update, rerun, and delete reject an active hybrid model with 409. Use POST /tasks/{taskId}/cancel to cancel an active run. Cancellation records a durable cancelled status and requests compute shutdown; shutdown is asynchronous. An invalid input returns 422; physical infeasibility or solver failure found during execution is reported on the failed task. Existing read/write workspace permissions apply throughout. Hybrid input representation limits default to 32 MiB for request JSON and 128 MiB for the normalized request plus hydrated PV runtime. Exceeding either returns 413. Explicitly selected hybrid POST bodies are bounded before JSON parsing, including streamed uploads. Saved-input PUT bodies are parsed before the service identifies the hybrid row and applies its semantic size check. PV hydration likewise precedes the runtime-artifact check. These limits prevent oversized representations from being saved or queued; they are not a guarantee of peak memory use or a change to the scientific time resolution. The Python SDK forwards the payload without changing its keys:
The CLI uses the same explicit selector:
Omitting the new SDK argument or CLI flag preserves the existing engine default. Hybrid models also appear in the UI’s energy-model list. Their detail page shows study-period export, import, stored energy, and revenue when prices are supplied, with input/result JSON downloads and rerun controls. The standard PV editor and quick-run shortcuts retain their solar contract. The import comparison flow explicitly prepares a new native v2 solar study; it does not convert or rewrite an existing run’s saved result.

Dispatch, prices, and physical execution

surplus uses available PV and plant headroom. battery_schedule requests battery-terminal power, and poi_schedule requests a signed POI target. Each request is projected onto equipment, energy inventory, charging permissions, and network limits. Requested schedules are not guarantees of delivered power; inspect the executed results and limiting reasons. Charging policies are unrestricted, pv_only, or curtailed_pv_only. Grid charging also requires plant.allow_grid_charging=true and a physical bidirectional path. Storage availability, connection state, commissioning and retirement timestamps, auxiliaries, temperature limits, and capacity retention can further restrict operation. Accepted event segments cover each declared input interval, including its final remaining fraction. Commissioning and retirement boundaries use elapsed UTC time within that interval. Energy, inventory and aging use the same accepted durations; displayed Unix timestamps do not determine the integration duration. Source attribution tracks PV, grid, and initial stored inventory. With unrestricted charging, contemporaneous supplies mix proportionally at each DC bus and the connected AC pool. PV-only policies reserve fresh local PV for charging first, then fresh PV arriving through the AC network. Energy discharged by another battery retains its historical tags but does not count as fresh PV for charging eligibility. These are explicit inventory-accounting conventions; they do not trace individual electrons. Physical source permissions and equipment limits are enforced separately before the inventory advances. curtailed_pv_only measures the physical export opportunity without requested storage dispatch, using realized PV and equipment, POI, and network limits. A separate economic extraction cap neither creates eligible clipping nor removes clipping already present. Accepted charging must still use actual fresh PV and respect the extraction cap. AC-coupled storage can recover downstream collector or POI curtailment; upstream inverter clipping requires access at a supported DC boundary. Optimization requires explicit prices with currency, import/export prices, source, market, and location. It has bounded rolling horizons, solve-time and relative-gap settings, a terminal inventory policy, and optional discharged-energy wear cost. Revenue uses executed POI import/export; the planning objective and predicted trajectory are reported separately. Finite-time solutions need not be proven optimal. forecast_mode defaults to perfect_foresight; realized_rolling_proxy is also an explicitly optimistic use of realized inputs. For historical forecast replay, choose forecast_replay and supply unambiguous, already-issued forecast snapshots covering each planning horizon. Missing time-varying forecasts do not silently fall back to realized data. The planner uses radial, prescribed-voltage branch bounds and simplified efficiencies; the executor applies actual losses and physical restrictions before advancing battery state.

Calculate PV from weather and mixed modules

Instead of inline availability, a source can specify a zero-based runtime_block_index and the request can include an ordinary Daly PV input under solar_input. Referenced equipment, project, weather, and layout data are resolved within the authenticated workspace. Every source must select exactly one of available_dc_mw, available_ac_mw, or runtime_block_index. The calculated-PV adapter uses options.pv_policy="subarray_allocation". Homogeneous groups in a block’s dcInputs can specify different moduleId values, with one module type within each string. It allocates the inverter’s capacity among those groups and combines their results. This is a labeled allocation approximation; it does not establish numerical equivalence to an external simulator. Direct common-voltage mixed-module IV solving is not yet an available optional policy. The new hybrid contract defaults options.pv_rear_coordinate_convention to front_consistent, aligning the default bifacial model’s rear transverse axis with the front tracker-rotation convention. The default rear model always uses a flat axis internally, while tracking, backtracking and front irradiance transposition retain the actual signed source axis tilt. Tracker rotation and fixed-panel tilt are preserved; imported and shared inputs are not overwritten. The corrected convention rejects other bifacial models. Select retained explicitly for those models or to reproduce the existing rear irradiance coordinates; there is no silent fallback. This choice changes bifacial rear irradiance and yield and is recorded in simulation metadata and PV cache identity. The maintained calculator resolves the physical front surface for both monofacial and bifacial tilted-axis trackers. The frozen V1 deployment and its default calculation remain unchanged. Without a source’s optional spatial configuration, the adapter requires one resolved orientation and shading domain per block. Independent MPPT buses cannot be bridged by a DC-connected battery; DC storage requires a supported single common bus. Calculated PV timestamps must be regular interval centres, with at least two samples, matching the hybrid interval starts plus half their durations. Do not shift or relabel an incompatible weather time axis.

Multiple orientations on one inverter

Set plant.pv_sources[i].spatial.orientations to a map of names to full inline Daly layouts. Reference those names with orientationKey on that source’s solar_input.blocks[runtime_block_index].dcInputs groups. Omitted group keys use the block’s original layout. Every named layout must be used, and every referenced name must resolve. One MPPT may contain several module groups but must have one orientation and tracker-control policy. Different MPPTs on the same inverter can face east and west. Mixed orientations on one MPPT remain unsupported. For example, this source fragment accompanies groups with orientationKey values east and west on separate mpptIndex values:
The default fixed_nominal sharing policy retains the declared nominal group fractions. daily_irradiance multiplies each nominal fraction by that group’s pre-loss incident plane-of-array irradiance and normalizes each time step; dark periods use nominal fractions. Inverter current limits remain tied to the physical MPPT, even when its AC capacity share changes. Physical auxiliary consumption is counted once. Virtual startup limits scale with capacity share. This allocation approximation is reported in metadata; numerical agreement with an external simulator is not established. Optimization still uses simplified linear conversion for planning and the actual conversion laws for execution.

Optional table-to-string shading

Read GET /v2/shadingscenes/{scene_id}/spatial-manifest after a shading calculation completes. The authenticated manifest returns sceneInputSha256, stable tableHash values, canonical receiver outlines, and geometric partition counts. An unavailable or stale binding returns an error requiring a scene refresh. Geometric partitions are not module or electrical string counts. Add a table_shading object under the source’s spatial configuration. This fragment describes one 20-module string on one declared table; use the actual manifest hash and table identity rather than the illustrative values:
Declare module capacity explicitly and cover each table’s zero-based module ranges exactly once. Each string’s segment count must match its DC group’s stringLength, and every group must have its declared stringCount mappings. A string may span tables. Initially the mapped block must contain one physical inverter, and a table cannot be split across sources. Table planes and actual tracker axes must match the associated layout. Terrain-aware tracker scenes are supported when their shading run produced the v2 terrain artifact; the scene’s per-table angles and receiver base-plane offsets are then applied per DC group, and a scene computed before that controller must be refreshed first. A scene can still contain unmapped tables and obstructions that shade mapped receivers. Editing the scene or tracking settings invalidates its binding and requires recalculation and an updated hash. This is a table-uniform partition approximation: every module assigned to a table receives that table’s mean direct/circumsolar and partition factors. Module positions identify wiring; they do not resolve local shadows or bypass-diode I-V curves. Positive partition-minus-linear deficits are evaluated per table before module-weighted aggregation, avoiding double-counted linear shading. Diffuse and ground shading retain the explicit regular-row approximation. Bifacial rear irradiance retains the row model and does not consume scene obstructions. Old string maps, shading profile identifiers, and explicit directional fields cannot be combined with this mode. Use options.output_detail="component" for per-interval components[].pv_availability.blocks diagnostics. Each converter’s block lists group available DC, PV-only AC availability, temperatures, irradiance, allocation fractions and table shading losses. metadata.pv.spatial_topology carries the DC-input, MPPT, module, orientation, string and table identities once. These values precede battery dispatch, AC network losses and plant curtailment; components[].converters and plant time series describe actual executed flows. Component detail has a larger output budget, so use shorter diagnostic windows for large plants and summary output for annual studies. Both spatial features are opt-in. Omitting spatial preserves the existing calculation path unless the separate whole-scene screening policy below is explicitly selected. Enabling nonuniform table shading can change total plant energy through losses and inverter clipping; results are not rescaled to force the old plant total. Documented snake_case names and camelCase aliases are both accepted.

Whole-scene irradiance screening without electrical mappings

Solar-template preparation accepts options.pv_scene_shading_policy="whole_scene_irradiance" only with explicit electrical_model="collection_equivalent", fast screening and plant or summary output. The default remains explicit_spatial; an imported scene link alone does not select this approximation. This policy requires a linked 3D scene with electricalImpact: false, homogeneous conventional trackers and source controls matching the completed scene, including axis tilt, GCR, backtracking and rotation limit. Active wind stow, diffuse irradiance optimization, terrain-aware tracking, string maps, shading-profile identifiers and per-source spatial mappings are incompatible. The source template is preserved; preparation fails instead of disabling an incompatible setting. Preparation authenticates the scene’s original input snapshot, completed task and exact result receipt. The complete planned sun-position grid must include every receiver. One receiver-area-weighted beam-loss grid applies to each native block linked to that scene. The same geometry’s directional artifact supplies ground-reflected shading; sky diffuse and rear irradiance retain regular-row approximations. This does not recover table-to-string wiring, per-inverter shading or electrical mismatch. Each prepared PV source receives a scene_screening binding with algorithm version whole-scene-irradiance-1. It identifies the scene inputs, result, directional artifact, runtime layout and compact beam grid by their hashes. Submission authenticates and resolves the same resources again; changed inputs or artifacts require fresh preparation. The worker checks its calculation content against the submitted binding. The separate options.pv_bifacial_row_policy="scene_geometry" opt-in can supply a missing default-bifacial rear row count from authenticated parallel tracker geometry. Its default, source, still requires declared row geometry. The derived policy groups occupied longitudinal slices into transverse row runs and averages their edge fractions by collector area:
Disconnected runs and singleton slices remain in that mean. The effective count may be fractional but must be at least two; the table total is not a row count. Geometry must agree with the declared axis, chord and pitch. Declared source rows are never overwritten. The derivation supplies only a runtime finite-field approximation with provenance, preserving source GCR, pitch, tracker axis and table coordinates. It preserves averaged irradiance terms, not separate nonlinear energy calculations for each run. The default rear model alone uses an internal 0° axis; actual-axis tracking and transposition remain unchanged. Collection policy solar-collection-equivalent-2 retains monthly source PF magnitudes and recurring availability periods (at most 1,000 periods, each no longer than 8,784 hours / 366 days, validated before queuing). PF affects native inverter conversion and current-dependent collection losses. Non-unity PF does not establish a reactive direction: signed POI Q is unavailable, while inverter reactive magnitude is reported separately. Outages use partial overlaps with centered source-local weather intervals and reduce positive final POI export after collection losses and auxiliaries. Night imports and upstream physics remain unchanged. See the preparation guide for the energy-ledger fields and supported scope. The default reduced-network behavior is unchanged.

Read results and understand limits

The resource envelope uses camelCase; its outputs preserves the scientific hybrid-result-1 result, including snake_case keys and caller-supplied asset IDs. It contains summary, timeseries, battery_timeseries, battery_summary, components, electrical_audits, dispatch_plans, final_state, and metadata. options.output_detail controls summary, plant, or component detail. Energy balance residuals, separate losses, source attribution, actual revenue, limiting reasons, model assumptions, and algorithm version are available for review. Component detail also includes components[].operating_diagnostics for each accepted segment. Battery records distinguish beginning/end inventory and capacity, commandable DC-terminal charge/discharge capability, service availability, and actual charging, discharging or idle operation. Commandable capability includes battery availability and SOC reserve restrictions; required auxiliary discharge has a separate allowance. Both exclude shared converter, network and charging-source restrictions; neither promises power deliverable at the POI. Signed margins report headroom to configured battery, converter, branch, bus and POI limits. Positive means headroom; negative means an exceeded bound. Margins are neither clipped to zero nor classified using a feasibility tolerance. null means not evaluated because a limit is absent, equipment is inactive, or the quantity is not modeled. Cell voltage/current remain unmodeled in the inventory battery. Reduced-network voltages are imposed, whereas full-network voltages are solved; converter DC voltage uses the specified nominal bus voltage. Read metadata.operating_diagnostics for these shared boundary and sign conventions. Summary and plant detail do not construct these per-segment diagnostic records. metadata.runtime_breakdown separates local validation, preparation, chronology, post-chronology audits and result assembly. Optional optimizer, full electrical solve and audit subtotals are already included in those stages; do not add them again. Preparation includes native PV calculations and solver initialization. The timing scope excludes prior imports, final result-budget serialization, external serialization, persistence and orchestration; it is not API latency. Task status and list responses keep the run identity and result summary compact. Read GET /energymodels/{id} for the complete scientific result in outputs; GET /tasks/{id} does not duplicate its interval and component arrays. For optimized dispatch, planned_revenue values the applied plan at its forecast prices. forecast_value_of_executed_energy values the actual accepted energy at those same prices, while realized_revenue uses actual settlement prices. execution_correction_revenue and settlement_price_error_revenue separate these two differences. These totals cover applied intervals; do not sum overlapping full-horizon values from dispatch_plans. metadata.electrical_coverage identifies checked snapshots and compact ranges of unchecked intervals. Default electrical audits retain observed extremes of PV, charging, discharge, reactive power, internal power flow, SOC, and the bounded set of encountered equipment states described by metadata.audit_selection. An audited snapshot does not certify unchecked subsegments or transient behavior. To assess storage recovery, compare otherwise identical executed studies with an explicit no-recovery counterfactual. Keep captured charge, discharged energy, incremental net POI energy, initial/final inventory, and losses separate. Adding charge and discharge counts the same stored energy twice; their annual ratio is not an intrinsic round-trip efficiency. Curtailment energy is attributed sequentially to outage, an optimized economic PV command, converter, POI, then network constraints. If later physical adjustments recover PV, earlier assigned curtailment is removed in reverse order. All encountered binding flags remain available. This is a consistent accounting rule, not an independent counterfactual estimate of each constraint’s marginal effect. Results have a 32 MiB compact-JSON budget. Admission estimates the combined study length, equipment count, audits, and retained optimization plans; an oversized request returns 422. Choose summary for large studies, or explicitly reduce the study window or retained audit/plan frequency. Summary output retains solver diagnostics but omits predicted optimization trajectory arrays. An exact byte check also runs during execution; exceeding it fails the task without publishing a partial result. The engine does not silently change temporal resolution. Each optimization horizon is limited to 50,000 interval-asset cells, using the largest battery, converter, or branch count. Reduce horizon_intervals explicitly when necessary; rolling execution still covers the requested study. API-run metadata includes the immutable input artifact reference and submission fingerprint so the stored result can be traced to its exact runtime inputs. Use GET /energymodels/{id}?include=inputs to retrieve the saved normalized configuration together with the result, and the model/task status to establish execution status. These API records complement the scientific result: the local executor does not duplicate the full input time series in its output. Model assumptions, optional solver status and fallback metadata, and the validity matrix below describe the interpretation and limits of the calculation. The inventory model does not solve cell electrochemistry or a dynamic thermal balance. Prescribed temperature and ambient-plus-offset are explicit assumptions; optional HVAC thermal service has an electrical cost but does not calculate cell temperature. The capacity-retention model is a configured calendar/throughput approximation. Equipment evidence and efficiency/derating curves improve input traceability, but synthetic examples are not validated equipment models. Reduced electrical analysis requires a connected radial AC network and imposes voltage rather than solving regulation. Full electrical analysis solves balanced AC operating points; it is not electromagnetic-transient or unbalanced three-phase simulation, and the DC side remains a controlled terminal/inventory model. Graph version 1 supports one AC/DC converter per shared DC bus. Study-specific accuracy and runtime should be established with representative equipment and operating points before relying on results for a design decision.

Model validity and validation coverage

The following matrix defines the supported calculation boundaries. Its covered checks describe analytical, differential, and execution tests; they do not certify a manufacturer’s equipment or establish accuracy for every accepted parameter combination. Input validation establishes a permissible model domain, not an equipment operating guarantee. A converged electrical solve alone is not a validation result. All chronological inputs require timezone-aware, contiguous intervals with their actual durations. A five-minute or hourly input represents conditions held over that interval; event splitting does not reconstruct faster weather, prices or electrical dynamics. Tests spanning a time axis do not establish a universal runtime bound or equipment calibration for that resolution. The minimum SOC is a dispatch reserve. Standing loss and physically supplied auxiliaries can reduce inventory below that reserve; the model does not invent energy to maintain it. Physical inventory still cannot become negative. Equipment evidence marked manufacturer_sourced or calibrated must identify its source, version, test conditions and operating domain; calibrated claims also require separate calibration and validation references. Daly validates that these descriptors exist, not their authenticity or applicability to a particular plant. Evaluate the chosen parameter domain, topology and timestep with independent equipment/reference evidence before treating a screening model as calibrated.