diff --git a/src/ac/ed/lurg/InternationalMarket.java b/src/ac/ed/lurg/InternationalMarket.java index 69788430c3f057cc19024bad161586a1ccd57b2f..36d9bd57207ed673059a67a20f004c7b32565f8e 100644 --- a/src/ac/ed/lurg/InternationalMarket.java +++ b/src/ac/ed/lurg/InternationalMarket.java @@ -169,7 +169,7 @@ public class InternationalMarket { GlobalPrice prevCPrice = carbonPrice; LogWriter.println(timestep.getYear() + " Updating carbon price", 2); GlobalPrice adjustedCPrice = prevCPrice.createWithUpdatedMarketPrices(totalCarbonImport, totalCarbonExport, timestep, - totalCarbonSequestered, false, 0.2); + totalCarbonSequestered, false, ModelConfig.DEFAULT_STOCK_USE_RATIO); LogWriter.println( String.format("Price for carbon updated from %s to %s \n", prevCPrice, adjustedCPrice), 2); if (adjustedCPrice.getStockLevel() < 0) LogWriter.println("Global stocks are below zero carbon, " + timestep.getYear(), 2); @@ -195,7 +195,7 @@ public class InternationalMarket { GlobalPrice prevTPrice = woodPrices.get(woodType); LogWriter.println(timestep.getYear() + " Updating " + woodType.getName() + " price", 2); GlobalPrice adjustedTPrice = prevTPrice.createWithUpdatedMarketPrices(totalWoodImport, totalWoodExport, - timestep, totalWoodProduction, true, 0.2); + timestep, totalWoodProduction, true, ModelConfig.DEFAULT_STOCK_USE_RATIO); LogWriter.println( String.format("Price for wood updated from %s to %s \n", prevTPrice, adjustedTPrice), 2); if (adjustedTPrice.getStockLevel() < 0) LogWriter.println("Global stocks are below zero wood, " + timestep.getYear(), 2); diff --git a/src/ac/ed/lurg/ModelConfig.java b/src/ac/ed/lurg/ModelConfig.java index 51f8d80ca045e206f4947eec9459d10c8892e19b..9de22fae3b4e41a62783797a1ead49c3235d2e0d 100755 --- a/src/ac/ed/lurg/ModelConfig.java +++ b/src/ac/ed/lurg/ModelConfig.java @@ -363,9 +363,9 @@ public class ModelConfig { public static final int BASE_YEAR = getIntProperty("BASE_YEAR", 2019); // Import export limits - public static final double ANNUAL_MAX_IMPORT_CHANGE = getDoubleProperty("ANNUAL_MAX_IMPORT_CHANGE", 0.5); + public static final double ANNUAL_MAX_IMPORT_CHANGE = getDoubleProperty("ANNUAL_MAX_IMPORT_CHANGE", 1.0); // Loose constraint as we have TRADE_ADJUSTMENT_COST_RATE public static final double MAX_IMPORT_CHANGE = getDoubleProperty("MAX_IMPORT_CHANGE", ANNUAL_MAX_IMPORT_CHANGE*TIMESTEP_SIZE); - public static final double TRADE_ADJUSTMENT_COST_RATE = getDoubleProperty("TRADE_ADJUSTMENT_COST_RATE", 0.02); + public static final double TRADE_ADJUSTMENT_COST_RATE = getDoubleProperty("TRADE_ADJUSTMENT_COST_RATE", 0.01); // Price caps public static final String PRICE_CAP_FILE = DATA_DIR + File.separator + "price_caps.csv"; @@ -404,10 +404,10 @@ public class ModelConfig { public static final String CONVERSION_COST_FILE = getProperty("CONVERSION_COST_FILE", DATA_DIR + File.separator + "conversion_costs.csv"); // cost of converting from one land cover to another, $1000/ha public static final double LAND_CONVERSION_COST_FACTOR = getDoubleProperty("LAND_CONVERSION_COST_FACTOR", 1.0); // for monte carlo public static final double AGRI_LAND_EXPANSION_COST_FACTOR = getDoubleProperty("AGRI_LAND_EXPANSION_COST_FACTOR", 1.0); // for monte carlo - public static final double CROPLAND_CONVERSION_COST = getDoubleProperty("CROPLAND_CONVERSION_COST", 0.11 * LAND_CONVERSION_COST_FACTOR); - public static final double PASTURE_CONVERSION_COST = getDoubleProperty("PASTURE_CONVERSION_COST", 0.08 * LAND_CONVERSION_COST_FACTOR); - public static final double FOREST_CONVERSION_COST = getDoubleProperty("FOREST_CONVERSION_COST", 0.1 * LAND_CONVERSION_COST_FACTOR); - public static final double NATURAL_CONVERSION_COST = getDoubleProperty("NATURAL_CONVERSION_COST", 0.015 * LAND_CONVERSION_COST_FACTOR); + public static final double CROPLAND_CONVERSION_COST = getDoubleProperty("CROPLAND_CONVERSION_COST", 0.30 * LAND_CONVERSION_COST_FACTOR); + public static final double PASTURE_CONVERSION_COST = getDoubleProperty("PASTURE_CONVERSION_COST", 0.20 * LAND_CONVERSION_COST_FACTOR); + public static final double FOREST_CONVERSION_COST = getDoubleProperty("FOREST_CONVERSION_COST", 0.50 * LAND_CONVERSION_COST_FACTOR); + public static final double NATURAL_CONVERSION_COST = getDoubleProperty("NATURAL_CONVERSION_COST", 0.01 * LAND_CONVERSION_COST_FACTOR); public static final double TECHNOLOGY_CHANGE_ANNUAL_RATE = getDoubleProperty("TECHNOLOGY_CHANGE_ANNUAL_RATE", 0.002); public static final int TECHNOLOGY_CHANGE_START_STEP = getIntProperty("TECHNOLOGY_CHANGE_START_STEP", 0); @@ -425,10 +425,12 @@ public class ModelConfig { public static final boolean RESET_ENERGYCROP_PRICE = getBooleanProperty("RESET_ENERGYCROP_PRICE", true); // Resets price after calibration to avoid problems due to low initial demand // public static final double BIOENERGY_HEATING_VALUE_GJ_PER_T = getDoubleProperty("BIOENERGY_HEATING_VALUE_GJ_PER_T", 17.5); // GJ per t DM - public static final double MARKET_BETA = getDoubleProperty("MARKET_BETA", 0.015); // controls international market price adjustment rate public static final double MARKET_LAMBDA = getDoubleProperty("MARKET_LAMBDA", 0.4); // controls international market price adjustment rate + public static final double EXPORT_ADJUSTMENT_RATE = getDoubleProperty("EXPORT_ADJUSTMENT_RATE", 1.0); // Tries to guide export levels towards import levels + public static final double STOCK_ADJUSTMENT_RATE = getDoubleProperty("STOCK_ADJUSTMENT_RATE", 0.1); // Tries to guide stock levels towards target level + public static final double DEFAULT_STOCK_USE_RATIO = getDoubleProperty("DEFAULT_STOCK_USE_RATIO", 0.2); - public static final String PRICE_UPDATE_METHOD = getProperty("PRICE_UPDATE_METHOD", "StockUseRatio"); // Options: StockUseRatio, MarketImbalance, StockChange + public static final String PRICE_UPDATE_METHOD = getProperty("PRICE_UPDATE_METHOD", "StockFlow"); // Options: StockFlow, MarketImbalance, StockChange public static final double MAX_PRICE_INCREASE = getDoubleProperty("MAX_PRICE_INCREASE", 1.5); public static final double MAX_PRICE_DECREASE = getDoubleProperty("MAX_PRICE_DECREASE", .75); public static final int DEMAND_RECALC_MAX_ITERATIONS = IS_CALIBRATION_RUN ? 0 : getIntProperty("DEMAND_RECALC_MAX_ITERATIONS", 0); // 0 is original behaviour diff --git a/src/ac/ed/lurg/country/CountryAgent.java b/src/ac/ed/lurg/country/CountryAgent.java index f870f4319d178335171216ec2eefabfb2eb77603..728939e7932c883a2f8e1574f48f203fe26cfb88 100644 --- a/src/ac/ed/lurg/country/CountryAgent.java +++ b/src/ac/ed/lurg/country/CountryAgent.java @@ -165,9 +165,8 @@ public class CountryAgent extends AbstractCountryAgent { CropUsageData cropUsage = entry.getValue(); CropType crop = entry.getKey(); double baseTrade = cropUsage.getNetImportsExpected(); - double changeUp = 0; - double changeDown = 0; - + double changeUp, changeDown; + // max of supply overall, needed for when imports are supplying feed and change is zero to allow for production to replace imports // minimum 1 so trade balance can still change when supply is 0 double maxOfProdOrSupply = Math.max(cropUsage.getProductionExpected() + Math.max(baseTrade, 0), 1); @@ -177,6 +176,20 @@ public class CountryAgent extends AbstractCountryAgent { } else { changeDown = changeUp = maxOfProdOrSupply * ModelConfig.MAX_IMPORT_CHANGE; } + + if (crop.isImportedCrop()) { + // Guide export level towards import level + if (baseTrade < 0) { + double ratio = currentWorldPrices.get(crop).getExportAdjustment(crop.getStockToUseRatio()); + baseTrade *= ratio; + cropUsage.updateNetImports(cropUsage.getNetImportsExpected() * ratio); + } + + // If stock about to go negative temporarily increase import price + if (currentWorldPrices.get(crop).isStockCritical()) { + currentCountryPrices.get(crop).adjustImportPrice(2.0); + } + } if (CropType.ENERGY_CROPS.equals(crop) && baseTrade == 0) { // could apply this logic for all crops? changeDown = changeUp = ModelConfig.MAX_IMPORT_CHANGE * currentGen2EcDemand; @@ -207,17 +220,29 @@ public class CountryAgent extends AbstractCountryAgent { if (ModelConfig.IS_CARBON_ON) { double baseTrade = carbonUsageData.getNetCarbonImport(); - double changeUp = 0.0; - double changeDown = 0.0; + double changeUp, changeDown; + if (Math.abs(baseTrade) < 1e-3) { // to set initial trade when starting from zero changeUp = changeDown = getCurrentCarbonDemand(); } else { double maxOfProdOrSupply = Math.max(carbonUsageData.getCarbonCredits() + Math.max(baseTrade, 0), 1); - // double change otherwise we run into issues as not all countries can meet demand with domestic production changeUp = changeDown = maxOfProdOrSupply * ModelConfig.MAX_IMPORT_CHANGE; } + // Guide export level towards import level + if (baseTrade < 0) { + double ratio = globalCarbonPrice.getExportAdjustment(ModelConfig.DEFAULT_STOCK_USE_RATIO); + baseTrade *= ratio; + carbonUsageData.updateNetImports(carbonUsageData.getNetCarbonImport() * ratio); + } + + // If stock about to go negative temporarily increase import price + if (globalCarbonPrice.isStockCritical()) { + currentCarbonPrice.adjustImportPrice(2.0); + } + carbonTradeConstraint = new TradeConstraint(baseTrade - changeDown, baseTrade + changeUp); + } else { carbonTradeConstraint = new TradeConstraint(0, 0); } @@ -246,6 +271,18 @@ public class CountryAgent extends AbstractCountryAgent { changeUp = changeDown = maxOfProdOrSupply * ModelConfig.MAX_IMPORT_CHANGE; } + // Guide export level towards import level + if (baseTrade < 0) { + double ratio = globalWoodPrices.get(woodType).getExportAdjustment(ModelConfig.DEFAULT_STOCK_USE_RATIO); + baseTrade *= ratio; + woodUsageData.get(woodType).updateNetImports(woodUsageData.get(woodType).getNetImport() * ratio); + } + + // If stock about to go negative temporarily increase import price + if (globalWoodPrices.get(woodType).isStockCritical()) { + currentWoodPrices.get(woodType).adjustImportPrice(2.0); + } + woodTradeConstraints.put(woodType, new TradeConstraint(baseTrade - changeDown, baseTrade + changeUp)); } diff --git a/src/ac/ed/lurg/country/CountryPrice.java b/src/ac/ed/lurg/country/CountryPrice.java index 96590b50d5ac283fe556d5c1f281118a594ef4e7..bdd1b027c8e9f6f27e981576fb383f9bc27acbc9 100644 --- a/src/ac/ed/lurg/country/CountryPrice.java +++ b/src/ac/ed/lurg/country/CountryPrice.java @@ -87,6 +87,10 @@ public class CountryPrice { else return getExportPrice(); } + + public void adjustImportPrice(double factor) { + importPrice *= factor; + } @Override public String toString() { diff --git a/src/ac/ed/lurg/country/GlobalPrice.java b/src/ac/ed/lurg/country/GlobalPrice.java index fa1ea9f57e4d6058e0f6a6efa6263a865ecbe794..84256e7c5ead43aa42eaa86b1afcfe4a2fc5b674 100644 --- a/src/ac/ed/lurg/country/GlobalPrice.java +++ b/src/ac/ed/lurg/country/GlobalPrice.java @@ -100,21 +100,14 @@ public class GlobalPrice implements Serializable { if (!ModelConfig.MARKET_ADJ_PRICE || (ModelConfig.IS_CALIBRATION_RUN && thisTimeStep.getTimestep() <= ModelConfig.END_FIRST_STAGE_CALIBRATION)) { adjustment = 1; } - else if (ModelConfig.PRICE_UPDATE_METHOD.equals("StockUseRatio")) { - // MARKET_BETA controls how strongly prices adjust to stock imbalance - // MARKET_LAMBDA controls how quickly prices reach equilibrium - //adjustment = 1 - (ModelConfig.MARKET_BETA * (updatedStock - targetStock) + ModelConfig.MARKET_LAMBDA * stockChange) / production; + else if (ModelConfig.PRICE_UPDATE_METHOD.equals("StockFlow")) { // Sum of target stock and trade flow volume. Trying to avoid large swings in prices when stocks low double stockAndFlow = stockLevel + Math.max(newImports, newExportAmountBeforeLoss); - adjustment = 1 - (ModelConfig.MARKET_LAMBDA * stockChange / stockAndFlow - + ModelConfig.MARKET_LAMBDA * (stockChange - prevStockChange) / stockAndFlow - + ModelConfig.MARKET_BETA * (updatedStock - targetStock) / targetStock ); - - // Maximum price increase if projected stock below zero - if (updatedStock + stockChange + (stockChange - prevStockChange) <= 0) { - adjustment = ModelConfig.MAX_PRICE_INCREASE; - } + // (1) stockChange analogous to velocity of change + // (2) (stockChange - prevStockChange) analogous to acceleration of change + // We need (2) to avoid large changes in prices when trade imbalance is already reducing + adjustment = 1 - ModelConfig.MARKET_LAMBDA * (stockChange + 0.5 * (stockChange - prevStockChange)) / stockAndFlow; } else if (ModelConfig.PRICE_UPDATE_METHOD.equals("MarketImbalance")) { double ratio = stockChange/(production-stockChange); @@ -177,5 +170,17 @@ public class GlobalPrice implements Serializable { this.stockLevel = stock; } + public boolean isStockCritical() { // is stock projected to go below 0? + double stockChange = exportAmountBeforeLoss - transportLosses - importAmount; + return stockLevel + stockChange + (stockChange - prevStockChange) <= 0; + } + + public double getExportAdjustment(double targetStockUseRatio) { + double surplus = stockLevel - (stockLevel / stockUseRatio) * targetStockUseRatio; + double ratio = importAmount / (ModelConfig.EXPORT_ADJUSTMENT_RATE * exportAmountBeforeLoss + + ModelConfig.STOCK_ADJUSTMENT_RATE * surplus); + return ratio; + } + } \ No newline at end of file diff --git a/src/ac/ed/lurg/country/gams/GamsLocationOptimiser.java b/src/ac/ed/lurg/country/gams/GamsLocationOptimiser.java index 99f5c442cb91be783ed3c5c9c60669ae1afb8255..b802bd3a07ca38e72fe252f69cb4e7fced6251cf 100644 --- a/src/ac/ed/lurg/country/gams/GamsLocationOptimiser.java +++ b/src/ac/ed/lurg/country/gams/GamsLocationOptimiser.java @@ -638,7 +638,7 @@ public class GamsLocationOptimiser { double supply = getParmValue(woodSupplyP, wType.getName()); WoodUsageData woodUsageData = inputData.getCountryInput().getPreviousWoodUsageData().get(wType); woodUsageData.setProduction(supply); - woodUsageData.setNetImport(netImports); + woodUsageData.updateNetImports(netImports); newWoodUsageMap.put(wType, woodUsageData); } diff --git a/src/ac/ed/lurg/landuse/CarbonUsageData.java b/src/ac/ed/lurg/landuse/CarbonUsageData.java index 00f052704664c385adaee0ec3a3806cdbde3932c..f5df4272bb1e6787960443228b316b906258296f 100644 --- a/src/ac/ed/lurg/landuse/CarbonUsageData.java +++ b/src/ac/ed/lurg/landuse/CarbonUsageData.java @@ -26,4 +26,8 @@ public class CarbonUsageData implements Serializable { public double getNetCarbonFlux() { return netCarbonFlux; } + + public void updateNetImports(double netImports) { + this.netCarbonImport = netImports; + } } diff --git a/src/ac/ed/lurg/landuse/WoodUsageData.java b/src/ac/ed/lurg/landuse/WoodUsageData.java index 4d8034308583a4795eb234ed55c19ba83be56896..9d0d29ff06610b325ef8d4825eeca1e1706223f2 100644 --- a/src/ac/ed/lurg/landuse/WoodUsageData.java +++ b/src/ac/ed/lurg/landuse/WoodUsageData.java @@ -30,7 +30,7 @@ public class WoodUsageData implements Serializable { return netImport; } - public void setNetImport(double netImport) { + public void updateNetImports(double netImport) { this.netImport = netImport; }