A battery count is an incomplete input to a ground-operations plan. Four packs do not describe how quickly depleted packs become available again, whether charging jobs compete for a resource, or how much idle time an aircraft accumulates while that happens. A spreadsheet that divides the working day by a nominal flight duration can conceal all three problems.

This tutorial builds a small deterministic scheduler in Python. The motivating aircraft is the UA20 agricultural drone, but the model is deliberately independent of its electrical specifications and performance figures. Every duration below is invented for the example. The result is a way to ask better resource questions, not a prediction of UA20 productivity or a recommendation for a battery or charger configuration.

UA20 agricultural drone, side view from its associated product materials

Start with a resource question you can actually answer

Suppose an equipment quotation offers an aircraft, several packs and charging equipment. The useful software question is: under a stated operating model, when does the aircraft wait because no pack is available? That question has observable inputs and an inspectable answer. Asking how many hectares the package will cover introduces additional dependencies that this model does not contain.

Keep the scope to one aircraft and a finite number of simulated sorties. Each sortie uses one pack. Every pack starts available at the beginning of the simulation. After landing, that pack becomes eligible for a charging resource after a defined delay, then occupies one resource for a defined service duration. Packs can return to the available pool while the aircraft is using another pack.

This definition already reveals a necessary modeling decision. A charging resource means one independently schedulable unit of service capacity. It does not automatically mean one plug, one socket, one case or one item on a quotation. A physical device that serves several connected packs sequentially may represent only one resource. The resource mapping needs evidence from the actual configuration before a real planning exercise uses it.

Define the clocks before writing the loop

Use integer minutes throughout this example. Flight duration is the interval from launch to landing. Ground duration is the interval after landing before the aircraft could launch again if a pack were available. Cooling is a modeled delay between landing and eligibility for charging. Charging is the service duration after a resource accepts the pack.

These labels describe an abstraction, not a physical handling procedure. In particular, the example treats pack removal as occurring at landing and allows its modeled post-flight process to overlap the aircraft's ground interval. If the actual workflow requires additional shared labor or handling time, represent that dependency explicitly instead of interpreting the current code as permission to overlap tasks.

A pack can experience two different waits. Charger wait is the interval between charging eligibility and actual charging start. Aircraft battery wait is the interval between the aircraft being ready for its next sortie and a pack being available. They are related through the resource schedule, but they describe different entities. Adding them together and calling the total operational downtime would count some overlapping time twice.

State the scheduling policy explicitly

The aircraft selects the pack with the earliest availability time. If several packs have identical availability times, the lower numeric identity wins. A selected pack launches at the later of its own availability and the aircraft's readiness. This produces deterministic output, including the cases where several resources become available simultaneously.

Charging jobs are assigned in sortie order to the resource that becomes free first. Because this example has one aircraft and the same cooling duration for every sortie, charging eligibility is also in sortie order. That property matters. A later extension with pack-specific cooling delays cannot assume that processing returned packs in their arrival order also processes them in eligibility order.

This is a defined policy, not an optimization solver. It does not select the most advantageous pack based on temperature, state of health or future work. It does not reserve resources for a different aircraft. The value of the model comes from making one policy reproducible before adding more sophisticated decisions.

Implement the smallest useful scheduler

Two min-heaps are enough. One holds each pack's next availability time and identity. The other holds each charging resource's next availability time and identity. Python's standard-library heap queue removes the smallest tuple first, so the ordering rules are visible in the data structure rather than hidden inside a selection callback.

The function below has no network connection, controller dependency or device command. It accepts a bounded set of integer parameters and returns an event table. Rejecting Boolean values is intentional: Python treats them as integer subclasses, but accepting True as a pack count would make a configuration mistake look like a valid experiment.

"""Synthetic, offline single-aircraft resource scheduling example. Python 3.9+.""" from heapq import heapify, heappop, heappush def simulate(*, packs, chargers, sorties, flight, ground, cooling, charging): values = dict(packs=packs, chargers=chargers, sorties=sorties, flight=flight, ground=ground, cooling=cooling, charging=charging) for name, value in values.items(): minimum = 0 if name in ("ground", "cooling") else 1 if type(value) is not int or not minimum <= value <= 10000: raise ValueError(f"{name}: expected an integer in {minimum}..10000") if packs > 100 or chargers > 100 or sorties > 5000: raise ValueError("example size limit exceeded") batteries = [(0, identity) for identity in range(packs)] bays = [(0, identity) for identity in range(chargers)] heapify(batteries) heapify(bays) aircraft_ready = 0 rows = [] for index in range(sorties): pack_ready, pack = heappop(batteries) launch = max(aircraft_ready, pack_ready) land = launch + flight eligible = land + cooling bay_free, bay = heappop(bays) charge_start = max(eligible, bay_free) charge_end = charge_start + charging rows.append(dict(sortie=index + 1, pack=pack, bay=bay, launch=launch, land=land, aircraft_ready_before=aircraft_ready, battery_wait=launch - aircraft_ready, eligible=eligible, charge_start=charge_start, charger_wait=charge_start - eligible, charge_end=charge_end)) heappush(batteries, (charge_end, pack)) heappush(bays, (charge_end, bay)) aircraft_ready = land + ground return rows if __name__ == "__main__": # All numbers below are invented tutorial inputs, not UA20 specifications. for packs, chargers in [(2, 1), (3, 1), (3, 2), (4, 2)]: rows = simulate(packs=packs, chargers=chargers, sorties=6, flight=10, ground=2, cooling=5, charging=25) print(packs, chargers, [row["launch"] for row in rows], sum(row["battery_wait"] for row in rows)) 

The bounds protect an example endpoint or notebook from an accidental oversized run. They are software limits and have no relationship to aircraft capacity. A production service would also control request size, execution time and concurrent requests at its boundary. It should preserve the parameter set with the result rather than returning a table without its assumptions.

Read the output as a timeline

Running the complete file prints these four results. The first two columns are pack count and charging-resource count; the list contains launch times; the last number is accumulated aircraft battery wait. All times use the same synthetic minute unit.

2 1 [0, 12, 40, 65, 90, 115] 55 3 1 [0, 12, 24, 40, 65, 90] 30 3 2 [0, 12, 24, 40, 52, 65] 5 4 2 [0, 12, 24, 36, 48, 60] 0 

With two packs and one charging resource, the aircraft launches at zero and twelve. After the second landing and ground interval, it is ready at minute twenty-four. The first pack does not finish its modeled charging service until minute forty. The resulting sixteen-minute battery wait is a consequence of the supplied durations and the stated process boundary.

The third pack reduces early waiting because the simulation begins with another available pack. It does not increase the service rate of the single charging resource. Comparing only the first few sorties would make this initial inventory advantage look like a permanent capacity improvement. The event table lets you distinguish a startup buffer from a sustained service-rate change.

Check a capacity bound before trusting a long simulation

The synthetic aircraft cycle is ten minutes of flight plus two minutes on the ground. Even with unlimited packs and charging resources, that model cannot sustain more than five launches per hour. One resource that needs twenty-five minutes per service cannot replenish more than 2.4 packs per hour in sustained operation.

Two such resources raise the charging bound to 4.8 services per hour, assuming they are genuinely independent within the model. There is also a circulation bound: absent any charger queue, a pack takes ten plus five plus twenty-five minutes from launch to becoming available again. Three packs therefore impose a separate upper bound of 4.5 launches per hour under these assumptions.

These are necessary capacity bounds, not proofs that a particular schedule achieves them. Timing, discrete jobs and resource contention can make realized simulated throughput lower. They are useful because a simulation that exceeds a sustained bound after initial inventory effects have disappeared probably contains an accounting or scheduling error.

The fourth output shows no battery wait across six sorties. It does not show an indefinitely sustainable twelve-minute launch interval. The model starts with four available packs and ends after a short horizon. Extend the horizon before describing a steady pattern, and report where the initial condition stops materially affecting the result.

Keep resource traces, not only a final score

Each returned row identifies the pack, charging resource, launch, landing, eligibility, charging start and charging end. That is enough to reconstruct a resource timeline. For every pack, its next launch must occur at or after the previous charging completion. For every charging resource, the next service must start at or after its previous service ends.

For the aircraft, each launch must occur at or after the previous landing plus the ground interval. Every charging start must occur at or after the corresponding eligibility time. Battery wait and charger wait must both be nonnegative. These relationships are more informative test targets than checking only a total sortie count.

A compact result should retain both the event table and a summary derived from it. Useful summary fields include final landing time, accumulated aircraft battery wait and the distribution of charging-resource waits. State the denominator for every rate. Six launches over a window beginning at the first launch differ from six completed sorties over a window that includes final ground service.

Test the accounting where mistakes would change a decision

The most valuable tests exercise resource conflicts and boundaries. Use a single pack to force reuse. Use several packs with one resource to create a charging queue. Use two resources to check simultaneous availability and deterministic identity ordering. Repeat the same input and require the same complete event table.

Also test zero cooling and zero ground duration separately. They are permitted by the software model so that you can isolate a dependency; they are not suggested physical operating conditions. Zero flight or zero charging duration is rejected because it would erase the service process the model is intended to analyze.

Invalid input tests should include negative numbers, floats, Booleans, strings and values above the example bounds. Test the event relationships across a longer run with several resource combinations. A particular launch-time fixture catches an arithmetic regression; resource non-overlap catches a broader class of scheduling mistakes even when the final summary happens to look plausible.

Design a measurement sheet that can replace the invented inputs

For a real analysis, record timestamps for the same process boundaries used by the model. Capture aircraft-ready time independently of pack-ready time. Otherwise, a late launch caused by a different ground task may be incorrectly labeled as a battery shortage. Preserve the reason for an unavailable pack instead of reducing every absence to a single charge duration.

Observe eligibility, actual service start and service completion separately. A long gap before service starts is queue time. A long interval after service starts is service time. Combining them into one charging duration and then adding another queue model would represent the same delay twice.

Retain pack and resource identities, configuration revision and the source of each timestamp. Record missing observations as missing. Do not fill an unobserved cooling interval with the sample's five minutes or assign an uncertain resource to whichever identity makes the schedule fit. The model should expose incomplete measurements so a reviewer can decide whether the comparison is meaningful.

Add variability without losing comparability

Start by evaluating several clearly labeled parameter scenarios. A shorter and longer service duration can reveal whether the conclusion is sensitive to that input. Such scenarios are assumptions, not measured percentiles. If you later use a distribution fitted from observations, retain its sample scope and the method used to handle missing or censored records.

Random simulation introduces a comparison problem. Two equipment scenarios can appear different because they happened to receive different random service times. Use the same underlying sampled jobs for a paired comparison, record the random seed and run enough independent replications to describe uncertainty. A single random trace is an example of behavior, not a confidence interval.

The current implementation intentionally stays deterministic. Adding per-job durations also requires revisiting the ordering assumption. If charging eligibility can overtake an earlier job, a true event queue should release jobs when they become eligible, then apply the selected queue policy. Merely replacing constants with random numbers in this loop would silently change the scheduling semantics.

Understand the dependencies that the model leaves out

Several physical resources can constrain a real ground workflow at the same time. People may handle multiple tasks. Charging resources may share an upstream power constraint. Some packs may be unavailable for inspection or another reason. The aircraft may wait for something unrelated to batteries. None of those dependencies is represented by increasing the count in the chargers parameter.

Model an additional shared resource when it would change the decision. A power limit, for example, cannot be represented by assuming that two logical service slots always provide two independent full-rate services. The correct abstraction depends on verified equipment behavior and the actual installation. This article makes no claim about UA20 charger ratings, electrical compatibility, pack limits or charging procedures.

Do not convert the simulated sorties into area or application output without an independent model for those quantities. Mission duration, useful work, refill dependencies and travel can vary together. Multiplying a scheduling result by a headline area figure would combine two models whose conditions may not match.

Make the analysis useful to an integration team

A developer-facing planning tool should show its assumptions beside the result. Let a reviewer inspect which resource produced each wait and compare two event tables. Preserve the configuration version so a later equipment change cannot silently reinterpret an old result. Export a simple table with documented units before building an elaborate dashboard.

For an aircraft-specific discussion, use the UA20 product reference to identify the intended model, then obtain configuration-specific evidence for the resource mapping and measured inputs. Keep the resulting schedule separate from operating instructions and aircraft acceptance decisions. A software model can reveal what follows from its assumptions; it cannot supply missing equipment evidence.

The next useful improvement is usually a better observation of the limiting dependency. If charger queue time dominates the model, investigate the resource assumption and measurements. If another ground process controls readiness, represent that process first. Adding packs to an unexamined model can make its output look better while leaving the actual constraint untouched.

Disclosure: UNITED UAV sells the aircraft used as the context for this tutorial. All scheduling durations and example results are synthetic; they are not UA20 performance specifications or a report of UNITED UAV field testing. This article was prepared with AI assistance. The executable example and resource-accounting checks were run locally.