energyRt 0.90.0 (pre-release)
The first release since 0.50-beta, consolidating the unreleased 0.60-0.80 cycles. The modelling API is the one planned for 1.0; testing and debugging continue. Upgrading from 0.50 is not a drop-in: see Breaking changes.
License
- The per-object timeslice maps (
mTechTimesliceand its siblings,mCommTimeslice,mWeatherTimeslice), the technology activity and output domains (mvTechAct,mvTechOut),meqTechActSngand the availability-factor maps (meqTechAfLo,meqTechAfUp,meqTechAfFx, where the bound is one number per technology, region and year) are defined by a rule, the join of the small tables they are made of, and their tables are dropped at the end ofinterpolate_model(). Readers see the same tuples; the multimod route enumerates the rule itself and the saved scenario no longer carries the tables. The output and single-output domains keep their tables in a model with coarse processes.options(energyRt.map_rules = FALSE)keeps every table;options(energyRt.map_rules_verify = TRUE)checks each rule against the table it replaces. The availability-factor maps are decided per technology, region and year and recorded as rules without building their tables, in a sparse, unfolded interpolation; a full-year one-country model builds them about 3.5 times faster, with a quarter less peak memory. - Relicensed from AGPL-3 to Apache-2.0. Releases up to v0.89 remain AGPL-3.
New features
newHorizon(base_year = )marks one milestone as GIVEN rather than decided. The marked year is interpolated as any other but never reaches the solver, and the milestones before it are historical and enter no LP at all; both still anchor interpolation.solve_base_year()solves the base year on its own, andinterpolate_model(base_year_scenario = )carries what it decided into the projection as sunkstockat its true vintage, leaving the model’s declared@stockuntouched. With no source the projection still excludes the base year and starts from the model’s own stock.getData(base_year = TRUE)returns both, labelled by asourcecolumn.Solver preset
mm_cuopt_wsl_py: cuOpt on a local GPU through WSL, run through cuOpt’s Python interface. The solution is kept at a time limit, andgpu_save_warm_start = TRUE/gpu_warm_start = <file>save and continue from PDLP’s warm-start data.solve_by_sample()accepts a saved scenario as its first argument, or an explicitbase =: the scenario’s model drives the sequence and the scenario becomes the base, so each sample’s interpolated tables are stored as a row filter over it instead of a copy. The base must have its inputs sealed (seal_scenario()), which leaves it solvable, and only tables at or aboveenergyRt.param_ref_min_rowsqualify. A base that cannot be used falls back to copying and says why.solve_by_sample(ncap_floor =)solves the ensemble twice: freely, then against a floor ofncap_floortimes the samples’ own new capacity, so the spread becomes period variation at one plan rather than variation between plans.ncap_statpicks the mean or the median. The consensus is taken across all samples, so it does not depend on the order they were solved, and a sample that built nothing counts as a zero rather than being skipped. It is a floor, not a fix: a sample may still build above it, which marks a period the consensus plan cannot serve. The floored solves are stored as<sample>_fvariants beside the free ones and the free pass stays in$scenarios_free;$runsgains apasscolumn.ncap_floor = 0is the default and changes nothing.recast_model_calendar()rebuilds a model’s time-varying data on another calendar. Timeslice values are rates, so aggregation is a weighted mean and annual totals are conserved;rules =overrides it per column. TOPIA at 4 seasons instead of 96 hourly slices solves in 1.4 s rather than 408 s.subset_model_regions()keeps a declaration made above the region level (a@geoframe = "zone"supply or trade) when the whole group survives, instead of dropping it. A subset that splits such a group now errors and names the options.interpolate_model(geoscale = )does not do this yet.check_feasibility()reports unservable demand, missing process inputs and binding supply ceilings, on a model or an interpolated scenario;interpolate_model(check = TRUE)runs it.
Model building
- Three-part storage: charger, reservoir and discharger with their own capacities, costs and availability, coupled by
@duration,@inp2outand@inp2stg. - Vintages (
@vintage) and clusters (@cluster) expand a process into variants at interpolation;getVariants()lists them. - Resource tiers: a
commodityorweatherobject with@clustergets one balance per tier. - Weather transforms derive many series from one, and a weather object may be declared at a coarser region than the processes reading it.
- Transmission:
newACLine(),newDCLink(), an optional Kirchhoff voltage law, loss tranches (lossTranches()), route ratings (@trade$af), route ratings over a parent timeslice (@trade$afs) and weather factors of the ratings (@weather, read at the route’s source region). - Supply, import and export curves:
asSupplyCurve(),asImportCurve(),asExportCurve(). - Taxes and subsidies are indexed by instrument and can be levied on processes.
capandncapcharge technologies and trades (a trade in each region the levy is placed in); a storage’s capacity is levied by part withstg.cap/stg.ncap(reservoir),inp.cap/inp.ncap(charger) andout.cap/out.ncap(discharger). -
commodity@propertyfor physical properties; discount rates at any geoscale level; a WACC per technology withpaybackas its cost-recovery life. - Portable process definitions:
process_to_spec()/process_from_spec()(YAML/JSON),process_designer(),read_technology_xlsx()/write_technology_xlsx().
Time and space
- Calendars from timescales, including irregular calendars (months of 28-31 days), sampled calendars (representative days) and the new
m12_d365_h24andm12_d365_h24_1dpm. - Multi-level geography from geoscales: commodities and processes may sit at a coarser
@geoframethan the flows they touch, with auxiliary flows held in the finer cells beneath. - Region aggregation with clustering:
aggregate_model_regions(clusters = ),get_process_groups(),process_cluster_sweep(),model_clusters(). - Sub-territory solves from a filtered geoscale, and
subset_model_regions(). - Auxiliary flows sit at the auxiliary commodity’s timeframe: an annual emission is one variable per year, not one per hour.
Solving
Solve drivers:
solve_recursive(),solve_by_sample(),solve_by_region()andsolve_guided().First-order LP solvers (cuOpt, HiGHS PDLP), and
lang = "mm", which builds the problem matrix with multimod. Remote solving on the NEOS server and on cloud GPUs.Matrix presets (
backend = "mm") are solved bymultimod::solve_mps(): HiGHS in the R session or through Julia, cuOpt on a local GPU through WSL (mm_cuopt_wsl) or on a rented GPU (mm_cuopt_cloud,pyomo_cuopt_cloud).On the
lang = "mm"route the activity and output definitions (eqTechAfFx,eqTechActSng) and the per-process totals (eqTechOutTot,eqTechInpTot, the storage, supply, import, export and demand totals) are substituted while the matrix is built: their rows and the columns they define are not written, which reduces a full-year model several-fold. The preset fieldsubstitutenames the equations;character()builds every row. With a multimod that composes roll-ups, the commodity output and input totals (eqOutTot,eqInpTot) are substituted too.The matrix route removes
model.mps(and its compressed copy) once the solution is decoded; the index tables and the solution file stay. The preset fieldkeep_mps = TRUEkeeps it. A preset’smm_solverandwherechoose the solver and the place. A Pyomo- or JuMP-written matrix carries index tables (from Pyomo’s symbol map, from JuMP’s containers) and is decoded by multimod like its own;julia_mpswrites the JuMP matrix andjulia_cuopt_cloudsolves it on a rented GPU.interpolate_model()is about 6x faster on large models;fx_equ = TRUEwrites fixed bounds as single equality rows (about 25% fewer rows on hourly VRE models).MPS output (
solver_options$pyomo_mps),read_solution()for solutions produced elsewhere, anden_check_solve(),en_check_packages(),en_install_deps(),en_install_julia_pkgs()to check and set up the toolchains.interpolate_model(model, ..., base = REF)builds a scenario on an interpolated base when the objects added are constraints, cost terms, taxes or subsidies: only what they add is computed, the base’s tables are reused and its store is never written to, and the result equals a full interpolation. Anything else is interpolated in full, with a message saying why. The saved scenario names its base inscenario.yml.A scenario keeps the model as given in
@model: the variants, tiers and transform clones interpolation builds are rebuilt when a reader needs them. A saved scenario references its sealed model instead of copying it, also for models with vintages, clusters or a spatial sample.getObject()on a scenario returns the objects as defined.save_dataset(rehash = FALSE)andsave_dataset(overwrite = TRUE)stop when another store entry references the version they would rewrite, and name the dependents.base = REFalso takes new processes, commodities and other model objects: the per-object tables of the objects REF holds are reused and every map is rebuilt.base = REFtakes a sample of REF’s calendar or regions (a calendar of some of its timeslices, a filtered geoscale withboundary_prices): REF’s tables are reused filtered to the sample. Saved, such a scenario references the sealed REF’s tables through a row filter.
Results and project management
- A project registry keyed on path, with a lifecycle (
seal_*(),mark_delete(), …); objects are reachable by name (getScenario("base")). - Runs and artifacts:
scenario_runs(),scenario_solutions(),promote_solution(),import_solution(),drop_scenario_run(). - Results in Arrow stores, readable when larger than memory;
getData(run = )reads several runs at once. -
prepare_for_sharing()andstrip_user_info(). - The dataset store keeps versions by content under one name (
datasets/<name>/<hash>/): saving other content under a name adds a version instead of overwriting,load_dataset(name, hash = )pins one, and the registry lists every version. A references table beside the registry records which scenario points at which version. -
save_scenario()keeps a scenario’s large interpolated tables (numeric parameters, bounds and maps fromenergyRt.param_ref_min_rowsrows, 1e6 by default; weather and demand series fromenergyRt.ondisk_min_rows) in its own store and records their hashes inscenario.yml. A scenario built withinterpolate_model(base = )on a saved and sealed base references every table identical to the base’s instead of storing a copy, and keeps the rest.load_scenario()resolves the references; the lazy readers follow them; a writer that changes a table drops its reference.unseal_scenario()refuses while scenarios reference the base’s tables (force = TRUEto unseal anyway). References into the dataset store held by scenarios saved earlier still load; a weather or demand table whose version is gone is recomputed from the model. - A scenario seals its inputs and its outputs separately:
seal_scenario(x, inputs = TRUE, outputs = FALSE). With sealed inputs and open outputs it can still be solved under a new run label;save_scenario()then writes that run only, so scenarios built on it keep reading the same tables.unseal_scenario(x, inputs = FALSE, outputs = TRUE)reopens the runs alone. A scenario sealed before reads as both parts sealed. -
save_model()andsave_scenario()takemode = "ref"(default,options(energyRt.save_mode)) or"full"."ref"references a repository or model only when its exact content is a SEALED version, and copies it in otherwise;"full"copies everything in.save_scenario()no longer stores an unsealed model in the model store to reference it. Changed: a scenario whose model is stored but not sealed now carries its own copy. - A scenario whose model is a sealed version plus repositories added by
interpolate_model()references the sealed version and keeps only the added repositories (model: addedin its manifest);load_scenario()adds them back. -
list_versions()lists the versions of models, repositories and datasets with a status (current,in use,marked,unused), when they were sealed, how many entries reference them and their size (details = TRUEfor more).mark_delete(version = )marks superseded versions (sealed ones of models and repositories; a hash, or"unused"), anddelete_marked()deletes them when still unreferenced.delete_marked()also keeps an entry while any of its sealed versions has a dependent. -
list_dependents()replacesstore_dependents(): the verified dependents recorded on sealed versions (withrecursive = TRUE, transitively) plus the references found in manifests;prune = TRUEdrops stale records. - Unsealed saves of models and repositories record a save id instead of a content hash (
hashed: falsein the manifest), so saving a large model no longer serializes it for hashing. The content hash is computed when a decision needs it: saving onto a sealed entry, comparing with the sealed versions of a name, and sealing (which now loads and hashes the payload). An identical re-save of an unsealed entry is written again.options(energyRt.lazy_hash = FALSE)hashes every save. - A model whose repository holds unchanged objects of one sealed repository version (taken with
getObject()from a loaded sealed repository) references that version with the object names and the hash of the selected objects, verified byload_model(). Sealing a repository records each object’s hash for this. - Sealing a model or repository freezes its content as a version (
<entry>/<hash>/): unsealed saves update the entry in place and leave the sealed versions untouched, and references andload_*(hash = )reach a sealed version exactly. A sealed version records the models and scenarios that reference it, andunseal_model()/unseal_repository()list them and refuse while they exist unlessforce = TRUE. - Referenced tables read back into memory are dropped before the scenario shell (
scen.RData) is written. A shell aboveoptions(energyRt.shell_max_mb = 256)warns and names its largest slots. - The
mmroute no longer copies the scenario’s tables into the multimod model: multimod reads them from the scenario’s store or the dataset store as the matrix is built (multimod_from_energyRt(inMemory = FALSE)), a table held in memory is attached rather than dropped, and the memo size is the preset’sdata_cache_gb(32 by default). Once the matrix is written, the model, the memo and the scenario’s tables that are persisted on disk or in the store are released, so a solve, local or in the cloud, waits with the memory free.
Validation and comparison
-
validate_scenario_parameters()(structural versus advisory findings),verify_solution()with check groups,audit_coefficients(). -
compare_scenarios(),compare_models(),compare_inputs().
Reporting and visualisation
report()for every class and for models and scenarios (HTML, PDF, Word, TeX), with spatial and solved context (geoscale =,model =,scenario =).levcost(): analytic levelized cost of technologies, storage (LCOS) and trade (LCOT) per vintage, cluster and region; ex-post per variant on a solved scenario.draw()process diagrams;autoplot()for objects, calendars, horizons, scenarios and comparisons;plot_map(),plot_trade_map(),plot_trade_network().getMix("generation")includes technology consumption, so a process that consumes the balanced commodity appears as a sink instead of vanishing.style = "map"for supply, import, export, demand, technology and storage; trade maps takecommand any geoscale level, and separate stock from investable capacity.Shared
autoplot()settings:@descas subtitle, the object’s unit on the y axis, legends titled by the key they show.autoplot(<supply>):param =picks the quantity,style =the chart;type =is deprecated.sequence_coverage()reports what a solve sequence covered, one row per unit and element, andautoplot()draws it: a sampled calendar as a tile grid or a wall calendar, a region sample on its geoscale, a recursive walk as a horizon Gantt with the look-ahead years marked apart from the decided ones. Elements no unit covered stay visible as gaps.autoplot(<by_sample>, "histogram")draws a result’s spread across the samples, weighted by each sample’s coverage, with the free pass overlaid on the floored one whenncap_floor > 0.solve_scenario(wait = FALSE)launches the solver detached and returns at once, for running several scenarios or carrying on working.waitis a documented argument now, and is refused together withread.solution = TRUE,transient = TRUEor an externalsolver.dir, none of which can be honoured once the call returns before the solve does. Collect the result later withimport_solution(). Detached solving covers the backends that launch a solver process (GLPK, GAMS, Pyomo, JuMP).A run record now identifies the process that wrote it (
pid,pid_started,detached), so a run still being written can be told from one whose writer is gone. While a run is in flight, reading it, dropping it, re-solving into it and sealing the scenario’s outputs are refused rather than allowed to corrupt or overwrite it;clear_run()is the override, andget_status()reports a run whose process has gone without finishing ascrashed. A run recorded on another host refuses as well, since the process cannot be checked from elsewhere.get_status()reports the state of a saved object – sealed (for a scenario, inputs and outputs separately), marked for deletion, runs held, size on disk – for one model, repository, dataset or scenario, or for every saved object in the project. It reads the manifests, so it does not depend on the registry being current. For a derived scenario it separatesbase, which is provenance, from the tables it actually borrows, which are what block unsealing or deleting that base.The solve drivers report progress through progressr:
set_progress_bar()for a bar,verbose = TRUEfor a line per unit, either or both. ### Shipped dataTOPIA (was UTOPIA) rebuilt as one 11-region model: land as the scarce resource (
LANDS,LANDW) in place ofSOL/WIN, wind site clusters, vintages, fuel supply curves, a transmission network from the geoscale, and a hydrogen chain (H2ELC,STG_H2,EXP_H2). Tangled fromvignettes/topia-build.Rmd.New TOPIA lever
GREEN_H2: electrolysis capped by renewable generation in the same region and timeslice (hourly matching, not unbundling).
Breaking changes
-
solve_myopic()is renamedsolve_recursive(), withmyopic_objective()torecursive_objective()and the result class frommyopictorecursive. The driver steps through the horizon’s milestones carrying capacity forward, which is recursive dynamic; it is only myopic in its default special case (step = 1,overlap = 0), sinceoverlapgives a step look-ahead andstep > 1solves several milestones jointly. There are no aliases.Sequences already on disk are unaffected:
variant.ymlnow recordstype: recursive_step, andread_sequence()reads the oldermyopic_stepas the same driver. sliceistimeslice, and acalendaris required; thetsl2*()helpers moved to timescales.Storage:
vStorageStoreisvStorageLevel;@cap2stg,@chargeand@infloware refused (use@duration,@startLevelor asupply); flows are bounded by converter capacity (inp.af.up/out.af.updefault to 1).technology@aeffno longer takesstg2ainp,stg2aout,sinp2ainp,sinp2aout,sout2ainporsout2aout; they were never read for a technology. A storage’s own@aeffkeeps its couplings.@start,@endand@olifeare merged into@vintage; vintage and cluster labels must be declared.@geolevelis@geoframe, and a model carries aGeoscale.Taxes and subsidies are indexed by instrument;
newConstraintS()andnewSub()are removed.Storage auxiliary couplings are part-prefixed (
inp.cap2ainp, …).The first argument is
objectin 66 functions; the scenario API is verb-first (save_scenario(),load_scenario(), …) withen_registry; 46 helpers are no longer exported; the 0.5-era deprecations are removed.The Julia and Python backends exchange Arrow files (
Arrow.jl,pyarrow);arrow_formatis split intostorage_formatandexchange_format.UTOPIA is TOPIA (regions
W1/W2,C1-C6,E1-E3); the test tiernightlyisfull.topia$modules$goldenis no longer shipped. The retiring per-layout kits are a test fixture (tests/testthat/fixtures/topia_golden.rds); examples and articles usetopia$modules$electricity.TOPIA’s horizons are rekeyed:
$horizons$baseis$BY2025_50_by10and$unitis$BY2025.The cloud presets are renamed:
mmcloudismm_cuopt_cloud,mmcloud_crossoverismm_cuopt_cloud_crossover,mmcloud_pyomoispyomo_cuopt_cloud,mmcloud_pyomo_crossoverispyomo_cuopt_cloud_crossover; a solve with a former name stops and names its replacement.mm_engineis replaced bymm_solverandwhere; settings stored by earlier versions are still read.get_hf_token()andset_hf_token()are removed: usemmcloud::get_hf_token()andmmcloud::set_hf_token().
Bug fixes
open_project()returns the option values it replaced, as documented. It collected them from theset_*()calls, which report the previously SET value – empty for a working default never set explicitly – so on a fresh session the list came back empty and restoring a previous project from it did nothing.clear_run()takes a path as well as a scenario, and records the abandonment in the registry. It required a loadable scenario, which the cases it exists for – a run recorded on another machine, or a store in a layout this version no longer reads – often cannot provide, although only the run’s directory is ever read. The run’s registry row is updated from the rewritten record, so a listing no longer saysrunninguntil the nextrefresh_registry().newModel()compares the model’scurrencyagainst each object’s@units$costsat construction. The check existed but ran only ininterpolate_model(), so an object priced in another currency sat in a model until something unrelated failed much later.plot_trade_network()draws routes declared at any level of a geoscalemap. It placed nodes at one level – the finest, unlesslevel =named another – so a corridor declared where its commodity is balanced was dropped with a warning about regions “not inmap”.level =still selects one level, andplot_trade_map()already drew every level – the two views now agree.autoplot()refuses a bareunitsvalue instead of dropping it.unitsnames a base unit per commodity (units = list(ELC = "GWh")); a parameter name passed there –units = "cost", whereparam = "cost"is meant – was accepted and ignored, and the chart then drew something else in silence. A style that labels from the object says thatunitshas no effect on it.A
demandnaming no year was dropped instead of read:aggregate()drops NA groups, socheck_feasibility()andautoplot(style = "area")both stopped with an aggregation error. The requirement is now checked and reported atyear = NA, andyears =keeps it; the annual stack asks foryear =when the object names no window of its own.drop_solver_outputs()could delete the working files of a solve in progress:.art_suggest()labelled anyrunningrecord stale with no check of the owning process, and the cleanup unlinks exactly the suggested rows. Only a run whose process is demonstrably gone is now treated as reclaimable scratch.A column a slot does not have is reported with the object, the slot, the columns that look meant (
stock->inp.stock,out.stock,stg.stock; a near spelling) and every column the slot accepts. An object whose slot was assigned directly (@<-) is checked when it enters a repository or a model, instead of failing later in interpolation with an unrelated message.A storage’s stock without a vintage (
inp.stock,out.stock,stg.stock) was given to every vintage, so a storage with several vintages carried its stock once per vintage. It now goes to the earliest vintage only, as a technology’s and a trade’sstockalready did.A user constraint row in which no term has a member of its variable’s domain (a technology that cannot be built yet in that year) is dropped at interpolation, with a message, when it could not bind anyway (
<=a non-negative value,>=a non-positive one,==zero). Before, it reached the matrix as an empty row, which the matrix route refuses. An empty row that would make the model infeasible, or a constraint empty in every row, is an error. Rows the check cannot decide (a region or timeslice that sums the levels beneath it, a year lag or lead) are kept.A vintage’s
endended the capacity built in it as well as its investment window: capacity of a vintage whose window closed before a later milestone was gone in that milestone. Capacity now stands from its build year for its operational life (olife), whatever the window.newConstraint()discarded a single-valuerhswheneverdefValwas set, anddefValis always set (given, or 0 /Infby default), sorhs = 100built a constraint at 0, or with no limit for<=. A single value is now the right-hand side of every row and replacesdefVal, with a warning when they differ.A weather series at a coarser timeframe than the availability bound it multiplies (
waf) read as zero and silently switched the technology off. It is now an error naming the series and the timeslices.unmark_delete()leftdelete_importanceandmarked_atbehind in the manifest, so an entry with no mark still carried an importance beside it and read as marked-at-some-tier to any reader that did not also testmarked_delete. The registry masked this, since it NAs the importance of an unmarked entry; the manifest did not. Both fields are now cleared, for an entry and for a version.interpolate_model(base =)refused every base when the caller also passedpath =. The base is dispatched before the control arguments are read out of..., sopathreached the addition check and was reported as “an object of class character is added; interpolating in full.” Every solve driver passespath, so none of them could build on a base, and because the refusal is a message it was invisible behind theirsuppressMessages(). An argument now counts as a model addition only if it is an energyRt object.A reloaded
solve_by_region()sequence served every variant the FIRST unit’s model. The scenario stored the model it was interpolated from, which the driver had already cut, while@modellives on the shell and is written only on the first save – so region R2’s results came back beside a model declaring R1, whose own R2 rows were empty.verify_solution()re-derives identities from that model, andlevcost()andreport()read its declarations. The driver now stores the full model with each unit’s region set, and the cut is rebuilt per variant.solve_by_region()accepted overlappinggroup =entries, or a repeated region inregions =, and solved and counted that region twice – including in the “sum of objectives” its print method presents as the whole-system cost. Overlaps are refused, naming the regions.sample_summary()’smeanis weighted by each sample’syear_fraction. Samples need not cover equal shares of the year –method = "sequential"folds a trailing singleton block into its predecessor – and the mean previously gave a larger block the same single vote. The quantiles remain unweighted, and a message says so when coverage varies.subset_model_regions()clears the parent’s recorded content hash. The subset keeps the model’s name on purpose andsave_model()updates an entry in place by name, so saving one could overwrite the full model’s store entry.read_sequence()no longer reports aby_regionsequence as complete while returning itsboundarystub counts asNA; they are declared missing.A spatial sample survives a variant switch: the problem cartridge records it, where before only the shell held it and a reload restored another unit’s regions.
A filtered geoscale announces the sub-territory solve unconditionally. The mode is accepted silently, so a geoscale covering only some declared regions used to yield a smaller solve – a different answer from the full model’s – with nothing said unless
verbose.About 130 fixes, most of which change results: unwired parameters now reach the solver, equations are no longer instantiated without data,
waf.fxbinds from both sides,aeff$cout2ainp/cinp2aoutbind, sub-region emissions are counted, coarse supply keeps its cost, andget_process_*(process = )is honoured.A tax or subsidy without
@regioncharged the coarse regions of a geoscale too, i.e. the roll-up of their sub-regions a second time. A commodity rate now applies at the commodity’s own regions and a process rate at the regions the process is declared in; a region off that level is an error.levcost()of a storage sized a charger with noinp2outorinp2stglike the discharger, and a discharger with nodurationat one hour of storage. Each is now sized by its own flow, as in the solved model.Auxiliary flows (
aeff) of a process were lost when it gave the coefficient for all timeslices and another process gave the same coefficient for named timeslices.Capacity taxes and subsidies on a storage charged nothing unless it had discharger data. They are now levied by part (see above); a levy that selects storages and sets only
cap/ncapis an error.A bound declared per region on one object and for all regions on another lost the second object’s bound equation (e.g. a fixed storage duration).
Content hashes of models with a geoscale are stable between R sessions (the S7 class object was hashed), so such models match their sealed versions in a new session.
Documentation
- A rebuilt pkgdown site with tutorial articles, and a runnable example on every public help page.
- The testing strategy (tiers, the
@coversmatrix, goldens) is documented intests/README.mdandtests/TESTING.md.
energyRt 0.50.9-dev
Breaking changes
- Time-slice weighting is rewritten: variables with a
slicedimension are no longer weighted, for consistency between sampled and non-sampled runs. Slice weights can vary across model years. - Variables with a
yeardimension are no longer weighted to interval lengths, except cumulative variables (vSupReserveCum) and capacity variables, which represent the state at the end of the period. - New-capacity variables (
vTechNewCap) are given per year — applypPeriodLento get total new capacity by the end of each period. - System costs are regrouped by type (capital, fixed O&M, variable O&M, supply, taxes, subsidies) and by process type, and the Total Costs equation rewritten to match.
Bug fixes
-
optimizeRetirment = TRUEno longer retires new technologies at the same time as their installation. -
draw()on a trade no longer repeats arrows. -
newCosts()is debugged, with an example in the Topia tutorial. -
tsl2hour()identifies n-digit hours; it previously worked for two only.
energyRt 0.50.7-dev
Bug fixes
- Several stability issues in
draw().
energyRt 0.50.6-dev
New features
-
draw()is drafted for every process class —technology,export,import,supply,demand,trade,storage.
energyRt 0.50.5-dev
New features
-
draw()is rewritten on thegridpackage and is now a generic, with methods fortechnology,exportandimport.
energyRt 0.50.3-dev
Documentation
- A
NEWS.mdfile to track changes;technology-classandnewTechnology()documented. Development version in preparation for CRAN submission.
