package MpmasMILPMultiperiod;

use strict;
use warnings;
use Exporter;
use Storable qw(dclone);
use Carp qw(croak);
use Data::Dumper;
use MpmasInputfile;
use MpmasInputBlock;
use MpmasInputBlockImmediate;
use MpmasInputBlockWithValueReplace;
use MpmasMILPAbstract;
use IndexedTable;
use SimpleObjectRegistry;
use MpmasMILPActivity;
use MpmasMILPConstraint;
use CtUtilities qw(numbers_robust_to_sorting stamp_time);
use MpmasQLClassDefinition;
use MpmasQLElementDefinition;
use MpmasQLFieldDefinition;

no warnings qw(experimental);
use feature qw(refaliasing);

use constant logMem => 0;
require Memory::Usage if logMem;

our @ISA = qw(MpmasMILPAbstract);



my %atcodes = (
	age_household_head 	=> 1,
	age_successor		=> 2,
	last_income			=> 3,
	mark_household_head => 4,
	mark_successor		=> 5,
	#extraChar			=> 6
);
my %aggcodes = (
	avg 	=> 0,
	sum		=> 1,
	med		=> 2,
	min		=> 3,
	max		=> 4,
	medl	=> 5,
	medu	=> 6
);
my %inserterOpCodes = (
	"*" 	=> 0,
	"/"		=> 1,
	"+"		=> 2,
	"-"		=> 3,
	"^"		=> 4
);
sub get_inserter_opcode {
	#my ($self, $arg) = @_;
	return $inserterOpCodes{$_[1]};
}


sub get_agg_method {
		my ($self, $agg) = @_;
		my $code = $aggcodes{$agg};
		print STDERR "Undefined aggregation method '$agg' in mpmas inserter\n" and exit(1) unless (defined $code);
		return $code;
}
sub get_lp_column_for_element {
	my ($self, $elementID) = @_;
	#print $self." ". $elementID."\n";
	my $c = $self->activity($elementID);
	print STDERR "Error: Unknown activity '$elementID' in mpmas_insert_value> \n" and exit(1) unless (defined $c);
	return $c->lp_column();
}
sub get_asset_object_id {
	my ($self, $elementID) = @_;
	#print $self." ". $elementID."\n";
	my $c = $self->networkobject($elementID);
	croak ("Error: Unknown asset  '$elementID'\n" ) unless (defined $c);
	return $c->oid();
}
sub get_innovation_id {
   	my ($self, $innovationName) = @_;
    my $i = $self->get_global("Innovations")->{$innovationName};
    croak ("Error: Unknown innovation group  '$innovationName' in inserter agent.innovation.access\n" ) unless (defined $i);
    return $i;
}
sub get_labgrp_id {
	my ($self, $h) = @_;
	my $lg = $self->config_entry("LABGRPS")->{$h}->{lid};
	print STDERR "Error: Unknown labgrp '$h' in mpmas_insert_value> \n" and exit(1) unless (defined $lg);
	return $lg; 
}
sub get_price_id {
	my ($self, $commodity) = @_;	
	my $i = $self->config_entry("PRICE_ITEM_LOOKUP")->{$commodity};
	print STDERR "Error in mpmas_insert_value>: No price defined for '$commodity' in PRICES [MPMAS TABLES]\n" and exit(1) unless(defined $i);
	return $i;
}
sub get_seasonal_data_id {
	my ($self, $code) = @_;	
	my $i = $self->config_entry("SEASONAL_ITEM_LOOKUP")->{$code};
	print STDERR "Error in mpmas_insert_value>: No seasonal data defined for '$code' in SEASONAL_DATA [MPMAS TABLES]\n" and exit(1) unless(defined $i);
	return $i;
}
sub check_look_ahead_market {
	my ($self, $t) = @_;
	print STDERR "Error in mpmas_insert_value>: markets.price. The second argument to the inserter requests to insert price data $t periods ahead, but your ADDITIONAL_PRICE_PERIODS entry is smaller than that [MPMAS TABLES]\n" and exit(1) unless($t <= $self->config_entry("ADDITIONAL_PRICE_PERIODS"));
	return $t;
	
}
sub check_positive {
	my ($self, $t) = @_;
	print STDERR "Error in mpmas_insert_value. Negative value in index column for an inserter that does not admit it in this place.\n" and exit(1) unless($t >= 0);
	return $t;
}
sub get_simulationRun_parameter_id {
	my ($self, $t) = @_;
	my $i = $self->get_simrun_parameter_id($t);
	print STDERR "Error in mpmas_insert_value>: simulationRun.parameter $t has not been defined\n" and exit(1) unless(defined $i);
	return $i;
}
sub get_simulationRun_parameter_default {
	my ($self, $t) = @_;
	my $i = $self->get_simrun_parameter_default($t);
	print STDERR "Error in mpmas_insert_value>: simulationRun.parameter $t has not been defined\n" and exit(1) unless(defined $i);
	return $i;
}
sub get_decision_stage_data_id {
	my ($self, $code) = @_;	
	my $i = $self->config_entry("STAGES_ITEM_LOOKUP")->{$code};
	print STDERR "Error in mpmas_insert_value>: No decision stage-specific data defined for '$code' in DECISION_STAGES_DATA [MPMAS TABLES]\n" and exit(1) unless(defined $i);
	return $i;
}
sub get_output_item_id {
	my ($self, $name) = @_;
	
	print STDERR "Error in agent.results.... Invalid output variable name '$name'\nOutput variable name should not contain spaces,newlines, etc.\nOnly a-z, A-Z, and _ are allowed\n" and exit(1) if ($name =~ /\W/);
	unless (exists $self->{resultVarIndex}->{$name}) {
		if (exists $self->{resultVarNextID}) {
			$self->{resultVarNextID} += 1;
		}
		else {
			$self->{resultVarNextID} = 0;
		}
		$self->{resultVarIndex}->{$name}= $self->{resultVarNextID};
	}
	return $self->{resultVarIndex}->{$name};
}

my %ExtraCharCodes;
my $ExtraCharCodesInitialized = 0;
sub init_household_attribute_ids {
	my ($self) =@_;
	my @ExtraChars = $self->get_cfg_list_empty_ok("EXTRA_HHMEMBER_CHARACTERISTICS");
	
	for my $i (0..$#ExtraChars) {
		$ExtraCharCodes{$ExtraChars[$i]} = $i;
	}
	$ExtraCharCodesInitialized = 1;
}

sub get_household_attribute_id {
	my ($self, $attribute) = @_;	
	$self->init_household_attribute_ids() unless ($ExtraCharCodesInitialized);
	my $atcode = $ExtraCharCodes{$attribute};
    print STDERR "Error: Household member attribute '$attribute' in 'agent.household.attribute' unknown. Attributes are:\n". join(', ', (keys %ExtraCharCodes))."\n" and exit(1) if (not defined $atcode);
	return $atcode;
}



sub get_agent_attribute_id {
	my ($self, $attribute) = @_;	
	
	my $atcode = $self->get_id_of_agent_attribute($attribute);
    print STDERR "Error: Agent attribute '$attribute' in 'agent.attribute' unknown. Attributes are:\n". join(', ',  $self->get_cfg_list("EXTRA_AGENT_ATTRIBUTES"))."\n" and exit(1) if (not defined $atcode);
	return $atcode;
}

sub get_cropact_attribute_id {
	my ($self, $attribute) = @_;	
	my $atcode =  $self->get_id_of_cropact_attribute($attribute);
    print STDERR "Error: Cropping activity attribute '$attribute' in 'agent.expectations.cropping ...' or 'agent.cropping...' unknown. Attributes are:\n". join(', ', $self->get_cfg_list("EXTRA_CROPACT_CHARACTERISTICS"))."\n" and exit(1) if (not defined $atcode);
	return $atcode;
}


sub explicit_zero_if_not_defined {
	my ($self, $arg) = @_;
	return $arg if (defined $arg);
	return 0;
}
my @defined_mpmas_insert_functions = (
	[("expression", 3,undef,\&get_inserter_opcode, undef )], #Note: Order important here: expressions must come first
	[("simulationRun.parameter", 1, \&get_simulationRun_parameter_id)], #then simulation run parameters second
	[("markets.price", 2, \&get_price_id, \&check_look_ahead_market )], # then market prices third (for firstmover updates to generic matrix=
	[("time.seasonal.data",2, \&get_seasonal_data_id)],  # then time seasonal data fourth (for firstmover updates to generic matrix=
	[("time.decision_stage.data",1, \&get_decision_stage_data_id)], #then decision stage data fifth (for firstmover updates to generic matrix=
	[("time.simulation_period.between",2 )], #then simulation_period,between sixth (for firstmover updates to generic matrix=
	[("time.simulation_period",0)], #then simulation_period seventh (for firstmover updates to generic matrix=
	
	
	[("agent.household.labor_capacity", 2, \&get_labgrp_id)],
	[("agent.household.minimum_consumption", 1)],
	[("agent.household.attribute", 2, \&get_household_attribute_id )],	 	#attribute, period
	[("agent.attribute", 1, \&get_agent_attribute_id )],	 	#attribute

#	[("agent.household.head_age", 0 )],			
#   [("agent.household.head_mark", 0 )],			
#	[("agent.household.successor_age", 0 )],	
#	[("agent.household.successor_mark", 0 )],
	[("agent.plots.area", 2 )],
	[("agent.plots.attribute", 3,undef,undef,\&get_agg_method )], #nru, user-defined, aggregation
	[("agent.plots.attribute_between", 5,undef,undef,\&get_agg_method )], #nru, user-defined, aggregation, lb, ub
	[("agent.plots.distance", 2,undef,\&get_agg_method )], #nru, aggregation
	[("agent.plots.distance_between", 4,undef,\&get_agg_method )], #nru, aggregation,  lb, ub
	
	[("agent.shared_plots.area", 2 )],
	[("agent.shared_plots.attribute", 3,undef,undef,\&get_agg_method )], #nru, user-defined, aggregation
	[("agent.shared_plots.attribute_between", 5,undef,undef,\&get_agg_method )], #nru, user-defined, aggregation, lb, ub
	[("agent.shared_plots.distance", 2,undef,\&get_agg_method )], #nru, aggregation
	[("agent.shared_plots.distance_between", 4,undef,\&get_agg_method )], #nru, aggregation,  lb, ub
	
	[("agent.assets.capacity", 2, \&get_asset_object_id, \&check_positive )],
	[("agent.assets.capacity_of_age", 2, \&get_asset_object_id, \&check_positive )],
#	[("agent.assets.capacity_of_ages_between", 3, \&get_asset_object_id )],
	[("agent.financial.cash_reserves", 0 )],
	[("agent.financial.depreciation", 1)],
	[("agent.financial.principal", 1 )],
	[("agent.financial.lt_interest", 1 )],		
	[("agent.financial.interest_rate_deposits", 0 )],
	[("agent.financial.interest_rate_short_credit", 0 )],
	[("agent.financial.interest_rate_long_credit", 0 )],
#	[("agent.plots.rentalPayments", 0 )],	
	[("agent.production_decision.solution", 1, \&get_lp_column_for_element)],
	[("agent.expectations.price", 1, \&get_price_id )],
	[("agent.expectations.yield", 3,undef,undef,\&explicit_zero_if_not_defined )],
	[("agent.trended_expectations.yield", 4,undef,undef,\&explicit_zero_if_not_defined )],
	[("agent.expectations.cropping_attribute", 3,undef,undef,\&get_cropact_attribute_id )], # cropmanagementid, nru , attribute
	[("agent.expectations.cropping_attribute_between", 5,undef,undef,\&get_cropact_attribute_id )], # cropmanagementid, nru , attribute, lb, ub	
	[("agent.trended_expectations.cropping_attribute", 4,undef,undef,\&get_cropact_attribute_id )], # cropmanagementid, nru , attribute, t
	[("agent.trended_expectations.cropping_attribute_between", 6,undef,undef,\&get_cropact_attribute_id )], # cropmanagementid, nru , attribute, lb, ub, t
	[("agent.cropping_attribute", 3,undef,undef,\&get_cropact_attribute_id )], # cropmanagementid, nru , attribute
	[("agent.cropping_attribute_between", 5,undef,undef,\&get_cropact_attribute_id )], # cropmanagementid, nru , attribute, lb, ub
	[("agent.yield", 3,undef,undef,\&explicit_zero_if_not_defined )],
	[("agent.livsim.milk_production", 0)],
	#[("agent.livsim.milk_yield_by_asset", 2, \&get_asset_object_id )] #assetId, age
	[("agent.innovation.access",1, \&get_innovation_id  )]
);
my @defined_mpmas_read_functions = (
	[("agent.results.aggregate", 1, \&get_output_item_id)], ##must be first
	[("agent.labor.off_farm",1)],
	[("agent.labor.on_farm",1)],
	[("agent.capital.off_farm",0)],
	[("agent.capital.borrowed",0)],
	[("agent.cash_consumption",0)],	
	[("agent.income.before_taxes",0)],		
#	[("agent.income.after_taxes",0)],
	[("agent.income.taxes",0)],
	[("agent.income.off_farm",0)],	
	[("agent.short_term_interest_paid",0)],	
#	[("agent.accounting.depreciation",0)],	
#	[("agent.accounting.principal",0)],	
#	[("agent.accounting.lt_interest",0)],	
#	[("agent.cashflow",0)],	
	[("agent.cash_carry_over",0)],	
	[("agent.production.crop_growth",2)],
	[("agent.assets.invest", 2, \&get_asset_object_id ,\&explicit_zero_if_not_defined)],
	[("agent.assets.maintain", 2, \&get_asset_object_id )],
	[("agent.assets.sell", 2, \&get_asset_object_id )],
	[("agent.assets.transform", 3, \&get_asset_object_id,  undef, \&get_asset_object_id  )],
	[("agent.innovations.use", 2, \&get_asset_object_id )],
	
#	[("agent.results.to_external", 2, undef , \&get_output_to_external_item_id)],
#	[("agent.mark.household_head", 0)],
#	[("agent.mark.successor", 0)]
	[("agent.herd.grazing",4)], #herdid/feedgroup, time, priority, nruId
	[("agent.herd.grazing_shared",4)], #herdid/feedgroup, time, priority, nruId
	[("agent.herd.feeding",4)], #herdid/feedgroup, feedtype, feedid, month
	[("agent.herd.assign_member",3,\&get_asset_object_id)],# object_id, object_age, feedgroup,
	[("agent.attribute.set", 1, \&get_agent_attribute_id)],
	[("agent.attribute.add", 1, \&get_agent_attribute_id)],
	[("agent.attribute.subtract", 1, \&get_agent_attribute_id)]
);

my %hash_mpmas_insert_functions = map { $_->[0] => $_   }  @defined_mpmas_insert_functions;
my %hash_mpmas_read_functions = map { $_->[0] => $_   }  @defined_mpmas_read_functions;

my $count = 0;
my %index_mpmas_insert_functions = map { $_->[0] => $count++   }  @defined_mpmas_insert_functions;
$count = 0;
my %index_mpmas_read_functions = map { $_->[0] => $count++  }  @defined_mpmas_read_functions;

sub get_index_of_inserter {
    unless (exists $index_mpmas_insert_functions{$_[1]}) {
			print STDERR "Error: found undefined mpmas inserter:\n";
			print $_[1],"\n";
	}
    return $index_mpmas_insert_functions{$_[1]};
}
sub get_index_of_reader {
    unless (exists $index_mpmas_read_functions{$_[1]}) {
			print STDERR "Error: found undefined mpmas reader:\n";
			print $_[1],"\n";
	}

    return $index_mpmas_read_functions{$_[1]};
}

sub sort_constraint_objects {

	my $self = $_[0];
    $self->make_constraint_order_hash();

    my $robustNames = {};
    for my $element (@{$self->constraints_by_ref}){
		my $x =$element->attr("type");
		my $y = $element->id;
		print STDERR "Error: constraint type $x not listed in #sort_order_equations (Constraint $y)\n" and exit(1) unless $self->{conrank}->{$x};


		print STDERR "Error Constraint $y has been defined twice!\n" and exit(1) if (exists $robustNames->{$y});
		
		eval {   
		    use warnings FATAL => qw/uninitialized numeric/;
			my $test = $element->attr("mperiod")+0; #+0 used to invoke test for numeric 
		};		      
		print STDERR "Error in mperiod attribute of constraint ".$element->id.":\n". MpmasInputfile::shortError($@)."\n" and exit(1) if $@;
		
		$robustNames->{$y} = numbers_robust_to_sorting($y);
	}



    MpmasInputfile->sort_constraint_objects(\&element_sorter, $self->{conrank}, $robustNames);
    
    delete $self->{conrank};
}
sub sort_activity_objects {

	my $self = $_[0];
    $self->make_activity_order_hash();

    my $robustNames = {};
    for my $element (@{$self->activities_by_ref}){
		my $x =$element->attr("type");
		my $y = $element->id;
		print STDERR "Error: activity type $x not listed in #sort_order_decision_variables (Activity $y)\n" and exit(1) unless $self->{actrank}->{$x};


		print STDERR "Error: Activity $y has been defined twice!\n" and exit(1) if (exists $robustNames->{$y});
		
		eval {   
		    use warnings FATAL => qw/uninitialized numeric/;
			my $test = $element->attr("mperiod")+0; #+0 used to invoke test for numeric 
		};		      
		print STDERR "Error in mperiod attribute of activity ".$element->id.":\n". MpmasInputfile::shortError($@)."\n" and exit(1) if $@;
		
		$robustNames->{$y} = numbers_robust_to_sorting($y);
	}



    MpmasInputfile->sort_activity_objects(\&element_sorter, $self->{actrank}, $robustNames);
    
    delete $self->{actrank};

}

sub element_sorter {
    my ($a, $b, $conrank, $nameRobust) = @_;
    return (
                  $a->attr("mperiod") <=> $b->attr("mperiod")
            ||    $conrank->{$a->type()} <=> $conrank->{$b->type()}
            ||    $nameRobust->{$a->id()} cmp $nameRobust->{$b->id()}
    );
}





sub prepare_shared {
	my ($self, $singlemode) = @_;
	$self->set_config_entry("MATRIX_TRIPLETS", 1);
	$singlemode = 0 unless (defined $singlemode);
	my $mu = MpmasInputfile->getMemUsageObj();
	
	if (! defined ($self->config_entry("DISCOUNT_RATE") )) {
		$self->set_config_entry("DISCOUNT_RATE", $self->config_entry("INTEREST_SHDEP"));
	}
	
	if ($singlemode and $self->config_entry("DISCOUNT_RATE") == 0.02 ) {
		print STDERR "Info: Using default interest rate of 0.02 for discounting future periods\n";
	}
    $mu->record( "before sorting constraints") if logMem;
	if (MpmasMILPConstraint->lazy_constraint()) {
		    $self->sort_constraint_objects();
	} 
	else {
	    $self->make_constraint_order_hash();
     
	    my $ConstraintTypes;
	    for my $element ($self->constraints){
		    my $x =$element->attr("type");
		    my $y = $element->id;
    #Adapt watersupply constraints to irrigation season
    #		if ($x eq "MASwater") {
    #			$y =~ /(\d+)$/;
			    #$x = "MASwater1";
			    #$x = "MASwater2" if ($1 < $self->config_entry("SEASONSTART"));
    #
    #		}
		    print STDERR "Error: constraint type $x not listed in #sort_order_equations (Constraint $y)\n" and exit(1) unless $self->{conrank}->{$x};
		    print STDERR "Error Constraint $y has been defined twice!\n" and exit(1) if (exists $ConstraintTypes->{$y});
		    $ConstraintTypes->{$y}[0] = $self->{conrank}->{$x};
		    
		    eval {   
		           use warnings FATAL => qw/uninitialized numeric/;
			    $ConstraintTypes->{$y}[1] = $element->attr("mperiod")+0; #+0 used to invoke test for numeric 
		    };		      
		    print STDERR "Error in mperiod attribute of constraint ".$element->id.":\n". MpmasInputfile::shortError($@)."\n" and exit(1) if $@;
		    
		    $ConstraintTypes->{$y}[1] = $element->attr("mperiod"); 
		    $ConstraintTypes->{$y}[2] = numbers_robust_to_sorting($y)
	    }
	    #Sort activities and constraints
	    $self->sort_constraints($ConstraintTypes);
	    undef $ConstraintTypes;
	}
    $mu->record("after sorting constraints") if logMem;
	
	if (MpmasMILPActivity->lazy_activity()) {
	    $self->sort_activity_objects();
	}
	else {
	    $self->make_activity_order_hash();
	    my $actrank = $self->{actrank};
	    
	    my $ActivityRanking;	
	    for my $element ($self->activities){
		    #check for existence of type
		    my $x = $element->attr("type");
		    my $y = $element->id;
		    print STDERR "Error Activity $y has been defined twice!\n" and exit(1) if (exists $ActivityRanking->{$y});
		    print STDERR "Error for Activity $y: activity type $x not listed in #sort_order_decision_variables\n" and exit(1) unless $actrank->{$x};
		    
		    $ActivityRanking->{$y}[1] = $actrank->{$x};

		    eval {   
		           use warnings FATAL => qw/uninitialized numeric/;
		           $ActivityRanking->{$y}[0] =  $element->attr("mperiod") +0; #+0 used to invoke test for numeric 
		    };		      
		    print STDERR "Error in mperiod attribute of activity ".$element->id.":\n". MpmasInputfile::shortError($@)."\n" and exit(1) if $@;
	    
		    
		    
		    
		    $ActivityRanking->{$y}[2] =  numbers_robust_to_sorting($y) ; 
		    
        ##Moved to orow write out for efficient
    #		if ($singlemode) { #apply discount rate, which is normaly applied by MPMAS
    #				eval {
    #					use warnings FATAL => qw/uninitialized numeric/;
    #					$element->discount_orow_with($self->config_entry("DISCOUNT_RATE") );
    #				};
    #				print STDERR "Error in objective value of activity $y:\n",MpmasInputfile::shortError($@),"\n" and exit(1) if $@;
    #		}

	    }

	    my @SortedActivity= sort {
			     $ActivityRanking->{$a}[0] <=> $ActivityRanking->{$b}[0]
			    || $ActivityRanking->{$a}[1] <=> $ActivityRanking->{$b}[1] 
			    || $ActivityRanking->{$a}[2] cmp $ActivityRanking->{$b}[2] 
    #			|| numbers_robust_to_sorting($a) cmp numbers_robust_to_sorting($b) 
			    
		      } keys %{$ActivityRanking} ; ##
	    my $g = -1;
	    for my $i (@SortedActivity){
		    my $x = $self->activity($i);
		    $x->set_lp_column(++$g);
		    #print  "Activity $i placed into column $g \n" if $::loudorder;
	    
	    }
	    print "\tActivities have been sorted\n" if $::loudorder;
	    
	    undef $ActivityRanking;
	    
	    $self->set_sorted_activities(\@SortedActivity);
    }
    $mu->record( "after sorting activities") if logMem;
	$self->calculate_milpinfo();
    $mu->record( "after prepare_shared: milpinfo") if logMem;

	  if ($self->config_entry("PERIODS_PER_YEAR") != 1 ) {
		if (defined  $self->config_entry("SEASONAL_DATA")) {
			my $seasdata = $self->config_entry("SEASONAL_DATA")->transform($self->get_dbh(), 3);
			my @items = $seasdata->query([],0);
			@items = sort {$a cmp $b} @items;
			my %itemIDs ;
			for my $i (0..$#items) {
				$itemIDs{$items[$i]} = $i;
			}
			$self->set_config_entry("CONVERTED_SEASONAL_DATA", $seasdata);
			$self->set_config_entry("SEASONAL_ITEM_LOOKUP", \%itemIDs);
			$self->set_config_entry("SEASONAL_ITEMS_SORTED", \@items);
	
		}
		else {
			$self->set_config_entry("CONVERTED_SEASONAL_DATA", IndexedTable->create_empty(3,"CONVERTED_SEASONAL_DATA", [("item","market","season","value")]));
			$self->set_config_entry("SEASONAL_ITEM_LOOKUP", {});
			$self->set_config_entry("SEASONAL_ITEMS_SORTED", []);
	
		}
	  }
	  if ($self->config_entry("ADDITIONAL_DECISION_STAGES") > 0 ) {
		if (defined  $self->config_entry("DECISION_STAGES_DATA")) {
			my $seasdata = $self->config_entry("DECISION_STAGES_DATA")->transform($self->get_dbh(), 2);
			my @items = $seasdata->query([],0);
			@items = sort {$a cmp $b} @items;
			my %itemIDs ;
			for my $i (0..$#items) {
				$itemIDs{$items[$i]} = $i;
			}
			$self->set_config_entry("CONVERTED_STAGES_DATA", $seasdata);
			$self->set_config_entry("STAGES_ITEM_LOOKUP", \%itemIDs);
			$self->set_config_entry("STAGES_ITEMS_SORTED", \@items);
	
		}
		else {
			$self->set_config_entry("CONVERTED_STAGES_DATA", IndexedTable->create_empty(2,"DECISION_STAGES_DATA", [("key","stage","value")]));
			$self->set_config_entry("STAGES_ITEM_LOOKUP", {});
			$self->set_config_entry("STAGES_ITEMS_SORTED", []);
	
		}
	  }

    $mu->record( "after prepare_shared: seasonal and stages data") if logMem;
}
sub prepare_special {
	my ($self, $singlemode, $curRHS, $rhshash, $newsinglemode) = @_;	
	
	$self->set_extension(".dat");
	$self->set_extension_cm(".txt");
	if ($self->tableau_compact) {
	    $self->set_extension(".cpt");
	}

	my $mu = MpmasInputfile->getMemUsageObj();
	my $starttime = MpmasInputfile->getStartTime();
	stamp_time($starttime, "before matrix title") if logMem;
	#$self->consistency_check_activities();
	$self->add_matrix_title($singlemode);
	stamp_time($starttime, "after matrix title") if logMem;
	$mu->record("after matrix title") if logMem;
	$self->add_objrow($singlemode);
	stamp_time($starttime, "after objrow") if logMem;
	$mu->record("after objrow") if logMem;
	$self->add_tableau($singlemode, $curRHS, $rhshash);
	stamp_time($starttime, "after tableau") if logMem;
	$mu->record("after tableau") if logMem;
	$self->add_fixed_independent_rhs() unless ($singlemode);
	stamp_time($starttime, "after fixed rhs") if logMem;
	$mu->record("after fixed rhs") if logMem;
	$self->add_type_and_range_of_constraints($singlemode, $curRHS, $rhshash, $newsinglemode);
	stamp_time($starttime, "after type and range") if logMem;
	$mu->record("after type and range") if logMem;
	#$self->add_marked_integers($singlemode);
	$self->add_column_bounds($singlemode) ;
	stamp_time($starttime, "after col bounds")if logMem;
	$mu->record("after col bounds")if logMem;
	$self->add_integer_sets();
	stamp_time($starttime, "after integer sets")if logMem;
	$mu->record("after integer sets")if logMem;
#	$self->add_multiperiod_timevariant_coefficients($singlemode);
	stamp_time($starttime, "after timevariants")if logMem;
	$mu->record("after timevariants")if logMem;
	$self->add_mpmas_insert_and_read_values($singlemode);
	stamp_time($starttime, "after inserters/readers")if logMem;
	$mu->record("after inserters/readers")if logMem;
	$self->add_simulation_specific_parameter_defaults($singlemode);
	stamp_time($starttime, "after simrun param defaults")if logMem;
	$mu->record("after simrun param defaults")if logMem;
	$self->add_mpmas_seasonal_database() unless ($singlemode);
	stamp_time($starttime, "after seasonal database")if logMem;
	$mu->record("after seasonal database")if logMem;
	$self->add_mpmas_decision_stage_database unless ($singlemode);
	stamp_time($starttime, "after decision stage database")if logMem;
	$mu->record("after decision stage database")if logMem;
}

sub add_matrix_title {
	my ($self, $singlemode) = @_;
	my $matrixtitle = MpmasInputBlock->new(8,1);
	$matrixtitle->add_comment_row("THE MATRIX FILE");
	$matrixtitle->add_comment_row("");
	$matrixtitle->add_comment_row("1", "PARAMETERS AND COEFFICIENTS"); 
	$matrixtitle->add_comment_row("1.1","Information on the size of the programming matrix","","","","","","","Number of constraints","Number of activities");			
	$matrixtitle->add_row("","","","","","","","",$self->milpinfo("NumRow"), $self->milpinfo("NumCol"),  $self->delim("#END_ROWCOL"));
	$matrixtitle->add_row("");
	
	#Workaround: For making the multi-period version also work as a plain simple LP without any discounting, without the user having to specify
	#the multiperiod entry at all (1 is default set by the Registry), we need to distinguish here the orinally assumed base case (x periods + period 0 = always at least 2 periods)
	#with the case where we de facto only have 1 period an that is period 0. [So no discounting is done.] This will still have MULTIPERIOD = 1, but none of the activities or constraints
	#will have an mperiod entry > 0.
	my $numMP = $self->config_entry("MULTIPERIOD") + 1;
	
	if (scalar @{$self->milpinfo("PeriodStartCols") } == 1 && scalar @{$self->milpinfo("PeriodStartRows")} == 1 &&  $self->config_entry("MULTIPERIOD") == 1) {
		$numMP = 1;
	}
#	elsif (scalar @{$self->milpinfo("PeriodStartCols")} < $numMP) {
#			print STDERR "Error: Highest mperiod attribute of activities ".scalar @{$self->milpinfo("PeriodStartCols")}." inconsistent with MULTIPERIOD entry: $numMP.";
#			exit(1);
#	}
#	elsif (scalar @{$self->milpinfo("PeriodStartRows")} < $numMP) {
#			print STDERR "Error: Highest mperiod attribute of constraints ".scalar @{$self->milpinfo("PeriodStartRows")}." inconsistent with MULTIPERIOD entry: $numMP.";
#			exit(1);		
#	}
#	elsif (scalar @{$self->milpinfo("PeriodStartCols")} > $numMP) {
#			print STDERR "Error: Highest mperiod attribute of activities ".scalar @{$self->milpinfo("PeriodStartCols")}." inconsistent with MULTIPERIOD entry: $numMP.";
#			exit(1);
#	}
#	elsif (scalar @{$self->milpinfo("PeriodStartRows")} > $numMP) {
#			print STDERR "Error: Highest mperiod attribute of constraints ".scalar @{$self->milpinfo("PeriodStartRows")}." inconsistent with MULTIPERIOD entry: $numMP.";
#			exit(1);
#	}
	
	
	unless ($singlemode) {
		$matrixtitle->add_comment_row("1.2","Information multi-period","","","","","","");			
		$matrixtitle->add_row("","","","","","","","",$numMP,  $self->delim("#END_MPERIOD"));
		$matrixtitle->add_row("");
		$matrixtitle->add_comment_row("1.3","Information on period ranges (activities)","","","","","","","First column Period 0", "...");			
		$matrixtitle->add_row("","","","","","","","",@{$self->milpinfo("PeriodStartCols")},  $self->delim("#END_MPERIOD_COLS"));
		$matrixtitle->add_row("");
		$matrixtitle->add_comment_row("1.4","Information on period ranges (constraints)","","","","","","","First row Period 0", "...");			
		$matrixtitle->add_row("","","","","","","","",@{$self->milpinfo("PeriodStartRows")},  $self->delim("#END_MPERIOD_ROWS"));
		$matrixtitle->add_row("");	
	}
	$matrixtitle->add_comment_row("1.8","Information on the shape of the programming matrix","","","","","","","Number of matrix blocks","Is the matrix transposed?  (yes=1 / no=0)","Index of  first constraint  in block 0","Index of  first activity  in block 0","Index of  first constraint  in block 1","Index of  first activity  in block 1","Index of  first constraint  in block 2","Index of  first activity  in block 2") ;	
	$matrixtitle->add_row("","","","","","","","",1,0,0,0, $singlemode ? "" : $self->delim("#END_INFOBLX")) if (not $self->config_entry("MATRIX_TRIPLETS"));
	$matrixtitle->add_row("","","","","","","","",0,  $self->delim("#END_INFOBLX")) if ($self->config_entry("MATRIX_TRIPLETS"));

	$matrixtitle->add_row("");
	$matrixtitle->add_row("");
	$self->add_block($matrixtitle);
	return $matrixtitle;
}

sub add_mpmas_insert_and_read_values {
	my ($self, $singlemode) = @_;
	

    my $temp = ${$self->get_inserter_registry()};
	my $tempread = $self->get_reader_registry();

	my $count = 0;
	
	for my $funcID (0..$#{$temp} ) {
		##check whether function exists
		#unless (exists $hash_mpmas_insert_functions{ $func } ){
		#	print STDERR "Error: found undefined mpmas inserter:\n";
		#	print $func,"\n";
		#} #check now done in get_index_of_inserter
		if($singlemode) {
			#if($func ne "expression" && $func ne "simulationRun.parameter")	{
			if($funcID > 1)	{
				print STDERR "Error: Mpmas inserter ". $defined_mpmas_insert_functions[$funcID]->[0]." not usable in static, standalone matrix mode.\n";
				exit(1);	
			}
		}
		for my $inserterInfo ( @ {$temp->[$funcID] } ) {
			$count++;
			if (exists $inserterInfo->{ao}) {
				$inserterInfo->{col} =  $inserterInfo->{ao}->lp_column();
				#if ($inserterInfo->{at} eq "orow") {
				#	$inserterInfo->{row} = -1;		
				if ($inserterInfo->{row} == -1) {			
					if ($singlemode) {
						$inserterInfo->{m}  /= (1+ $self->config_entry("DISCOUNT_RATE")) ** $inserterInfo->{ao}->{mperiod} if (defined $inserterInfo->{m});		
					}
				}
				#elsif ($inserterInfo->{at} eq "lbound") {
				#	$inserterInfo->{row} = -2;					
				#}
				#elsif ($inserterInfo->{at} eq "ubound") {
			#		$inserterInfo->{row} = -3;					
			#	}
			}
			elsif (exists $inserterInfo->{co}) {
				$inserterInfo->{row} =  $inserterInfo->{co}->lp_row();
				unless (exists $inserterInfo->{col} ){
					if ( MpmasMILPConstraint->has_coeffWithConstraints() ) {	
						my $c = $self->activity($inserterInfo->{at});
						print STDERR "Error: Unknown activity '$inserterInfo->{at}' referenced in inserter in subelement of constraint". $inserterInfo->{co}->id()."\n" and exit(1) if (not defined $c);

						$inserterInfo->{col} =  $c->lp_column();
					}
					else {
						print STDERR "Error: Unknown element '$inserterInfo->{at}' referenced in inserter in constraint ". $inserterInfo->{co}->id()."\n" and exit(1);
					}
				}	
			
			}
			elsif (exists $inserterInfo->{row} and $inserterInfo->{row} >= 0) {

				unless (exists $inserterInfo->{col} ){
					if ( MpmasMILPConstraint->has_coeffWithConstraints() ) {	
						my $c = $self->activity($inserterInfo->{at});
						print STDERR "Error: Unknown activity '$inserterInfo->{at}' referenced in inserter in subelement of constraint". $self->constraint_id_at_row($inserterInfo->{row})."\n" and exit(1) if (not defined $c);

						$inserterInfo->{col} =  $c->lp_column();
					}
					else {
						print STDERR "Error: Unknown element '$inserterInfo->{at}' referenced in inserter in constraint ". $self->constraint_id_at_row($inserterInfo->{row})."\n" and exit(1);
					}
				}	
			
			}
			
		}
	}
	
	

	
	my $block;
	my $numMilps = scalar @{ $self->milplist };

	my $NumRow = $self->milpinfo("NumRow");
	my $NumCol = $self->milpinfo("NumCol");

	if ( ($count > $self->config_entry("LIMIT_FOR_IMMEDIATE")  /2 /$numMilps )
		|| ( $NumRow   > $self->config_entry("LIMIT_FOR_IMMEDIATE")   /$numMilps ) 
		|| ( $NumCol   > $self->config_entry("LIMIT_FOR_IMMEDIATE")   /$numMilps ) 
		) {
		$block  = MpmasInputBlockImmediate->new(1,200, $self->get_skip_comments());
	}
	else {
		$block  = MpmasInputBlock->new(1,200);
	}
#	for my $f ( $singlemode ? @defined_mpmas_insert_functions[0,1] : @defined_mpmas_insert_functions) {
	for my $fID ( $singlemode ? (0..1) : (0..$#defined_mpmas_insert_functions) ) {
	    my $f = $defined_mpmas_insert_functions[$fID];
		$block->add_empty_rows(1);
		$block->add_row("", $f->[0].":");
		next unless (exists $temp->[$fID]);
		
		
		
		#if ($self->strict_sorting()) { ###ensure strict_sorting here, otherwise this leads to expressions not yet defined errors in MPMAS later on
			#$temp->{$fID} = [ sort {    $a->{col} <=> $b->{col} 
			#                           ||  $a->{row} <=> $b->{row} 
			#                           || ($a->{px} // -1)  <=> ($b->{px} //-1)
			#                           || ($a->{po} // -1) <=> ($b->{po} //-1 )
			#                          } @{$temp->{$fID} } ];
		
		
		    my @array ;
            \@array = $temp->[$fID];  #alias the list array into an array variable so that sort is using inplace sorting! # requires perl 5.22
			@array=  sort {    $a->{col} <=> $b->{col} 
			                           ||  $a->{row} <=> $b->{row} 
			                           || ($a->{px} // -1)  <=> ($b->{px} //-1)
			                           || ($a->{po} // -1) <=> ($b->{po} //-1 )
			                          } @array;


		
		#}
		
		for my $g (  @{$temp->[$fID] } ){
			
			my ($con, $act);
			
			if (exists $g->{ao}) {
				$act =  $g->{ao}->id();
				$con =   ("orow", "lbound", "ubound" )[ -1 * $g->{row} -1 ];
			}
			elsif (exists $g->{co}) {
				$con =  $g->{co}->id();
				$act = $g->{col} == -1 ? "RHS" : $self->activity_id_at_col( $g->{col} );
			}
			elsif (exists $g->{row} && $g->{row} > -1 ) {
				$con =  $self->constraint_id_at_row( $g->{row} );
				$act = $g->{col} == -1 ? "RHS" : $self->activity_id_at_col( $g->{col} );
			    
			
			
			}
					
			#check correct number of arguments
			if ($f->[1] != $#{$g->{ar}} +1 ) {
				print STDERR "Error: Invalid number of arguments ",$#{$g->{ar}} +1, " (", $f->[1] ,") for mpmas inserter ".$f->[0]." for ".$act."/". $con. "\n";
				exit(1);
			}
			#copy arguments
			my @args = @{$g->{ar}};
			#in-place transform of mpmasql argument to MPMAS argument if necessary
			for my $i (2..$#{$f}) {
				if (defined $f->[$i]) {
					eval {
						$args[$i-2] = $f->[$i]->($self, $args[$i-2]); 
					};					
					if ($@) {
						print STDERR "Error in mpmas inserter ". $f->[0]." for ".$act. "\n".MpmasInputfile::shortError($@)."\n";
						exit 1;
					}
				}
			}
			#check if numeric
     		for my $h (@args) {
				print STDERR "Error: argument $h for mpmas inserter function ".$f->[0]." for ".$act."/". $con. " isn't numeric\n" and exit(1) unless ($h =~ /^[-+.Ee\d]+/);
			}
			$block->add_row("", $g->{col}, $g->{row}, $g->{px}//-1, $g->{po}//-1, $g->{s}//0, $g->{m}//1, @args);			
		}
		#delete $temp->{$f->[0]};
	}
#	if (scalar(keys (%$temp)) > 0) {
#		print STDERR "Error: undefined insertion function(s) in mpmas inserter:\n";
#		print join("\n", keys (%$temp))."\n";
#		print Dumper($temp);
#		exit(1);
#	}
	$block->add_row("",$self->delim("#END_MPMAS_INSERT_VALUES"));
	$block->add_empty_rows(3);
	
    undef $temp;
	
	
	for my $funcID (0..$#{$tempread}) {
		##check whether function exists
		#unless (exists $hash_mpmas_read_functions{ $func }) {
		#	print STDERR "Error: found undefined mpmas reader:\n";
		#	print  $func,"\n";
		#} has already been done in get_index_for_reader
		for my $readerInfo (@ { $tempread->[$funcID] }) {
			$count++;
			$readerInfo->{col} =  $readerInfo->{ao}->lp_column();
			delete $readerInfo->{ao};
		}
	}
	
	
	
	
	#unless($singlemode) {
		#for my $f ( $singlemode ?    ($defined_mpmas_read_functions[0])      :   @defined_mpmas_read_functions) {
		for my $fID ( $singlemode ?    (0)      :   (0..$#defined_mpmas_read_functions)) {
		    my $f = $defined_mpmas_read_functions[$fID];
			$block->add_empty_rows(1);
			$block->add_row("", $f->[0].":");
			next unless (exists $tempread->[$fID]);
			if ($self->strict_sorting()) {
#				$tempread->{$fID} = [sort {$a->{col} <=> $b->{col} } @{$tempread->{$fID} }] ;
		        my @array ;
                \@array = $tempread->[$fID];
                @array = sort {$a->{col} <=> $b->{col} } @array;
			}
			for my $g (@{$tempread->[$fID ] } ){
				
				#check correct number of arguments
				if ($f->[1] != $#{$g->{ar}} +1 ) {
					print STDERR "Error: Invalid number of arguments ", $#{$g->{ar}} +1 ," for mpmas reader ".$f->[0]." for ". $self->activity_id_at_col($g->{col})  . "\n";
					exit(1);
				}
				#copy arguments
				my @args = @{$g->{ar}};
				#in-place transform of mpmasql argument to MPMAS argument if necessary
				for my $i (2..$#{$f}) {
					if (defined $f->[$i]) {
						eval {
							$args[$i-2] = $f->[$i]->($self, $args[$i-2]); 
						};
						if ($@) {
							print STDERR "Error in mpmas reader ". $f->[0]." for ".$self->activity_id_at_col($g->{col}). "\n".MpmMpmasInputfile::shortError($@)."\n";
							exit 1;
						}
					}
				}
				#check if numeric
				for my $h (@args) {
					print STDERR "Error: argument $h for mpmas reader ".$f->[0]." for ".$self->activity_id_at_col($g->{col})." isn't numeric\n" and exit(1) unless ($h =~ /^[-+.Ee\d\s]+$/);
				}

				$block->add_row("", $g->{col}, $g->{ob}, @args);			
			}
			#delete $tempread->{$f->[0]};
		}
	#	if (scalar(keys (%$tempread)) > 0) {
	#		print STDERR "Error: undefined read function(s) in mpmas reader:\n";
	#		print join("\n", keys (%$tempread))."\n";
	#		print Dumper($temp);
	#		exit(1);
	#	}

		$block->add_row("",$self->delim("#END_MPMAS_READ_VALUES"));
		
		$block->add_empty_rows(3);
		for my $v (sort {$a cmp $b } keys %{ $self->{resultVarIndex} } ) {
			$block->add_row("", $v, $self->{resultVarIndex}->{$v});
		}	
		$block->add_empty_rows(3);
		
		$block->add_row("",$self->delim("#END_MPMAS_OUTPUT_VARIABLES"));
	#}

#	for my $v (sort {$a cmp $b } keys %{ $self->{resultToExternalVarIndex} } ) {
#		$block->add_row("", $v, $self->{resultToExternalVarIndex}->{$v});
#	}	
#	$block->add_empty_rows(3);
	
#	$block->add_row("",$self->delim("#END_MPMAS_TO_EXTERNAL_VARIABLES"));


		
	$self->add_block($block);
	

	undef $tempread;
}
sub add_simulation_specific_parameter_defaults {
	my ($self) = @_;

	my @paramlist = @{$self-> get_simrun_parameter_list()};
	
	my $NumRow = $self->milpinfo("NumRow");
	my $NumCol = $self->milpinfo("NumCol");
	my $numMilps = scalar @{ $self->milplist };
	
	my $block;
	
	if ( (scalar(@paramlist) > $self->config_entry("LIMIT_FOR_IMMEDIATE")  /2 /$numMilps )
		|| ( $NumRow   > $self->config_entry("LIMIT_FOR_IMMEDIATE")   /$numMilps ) 
		|| ( $NumCol   > $self->config_entry("LIMIT_FOR_IMMEDIATE")   /$numMilps ) 
		) {
		$block  = MpmasInputBlockImmediate->new(3,230, $self->get_skip_comments());
	}
	else {
    	$block = MpmasInputBlock->new(3,230);
    }
	
	
	$block->add_row("SIMULATION RUN-SPECIFIC VARIABLE PARAMETERS");
	$block->add_row("1","","","");
	$block->add_row("","1.2", "Number of parameters", scalar(@paramlist));
	$block->add_row("","","",$self->delim("#END_SIMRUNSPECS"));

	$block->add_empty_rows(1);
	$block->add_comment_row("","","","Parameter name","Parameter ID","Default value");
	for my $k (@paramlist)
	{
		
		
		$block->add_row("", "", "",$k, $self-> get_simrun_parameter_id($k), $self-> get_simrun_parameter_default($k) );
	}
	$block->add_empty_rows(2);	
	$block->add_row("","","",$self->delim("#END_MPMAS_PARAMETER_DEFAULTS"));
	$self->add_block($block);
}



sub add_multiperiod_timevariant_coefficients {#not used anymore 221213 ct
	my ($self, $singlemode) = @_;
	my $timevariants = MpmasInputBlock->new(2,160);
	
	$timevariants->add_comment_row("19","INDEXED DATA FOR LP :");
	$timevariants->add_comment_row("","Note: This feature allows to change LP coefficients and RHS values  during simulation, e.g. improved technical coefficients starting in period 5");
	$timevariants->add_empty_rows(1);
	$timevariants->add_row("","Number of matrix elements to be changed", $self->get_number_of_exchanges()->{$self->argument} || 0);	
	$timevariants->add_comment_row("","", "period","LP column","LP row","value");

	for my $period (sort {$a <=> $b} keys %{$self->get_exchanges}) {
		for my $col (sort {$a <=> $b} keys %{$self->get_exchanges->{$period}->{$self->argument}}) {
			for my $row (sort {$a <=> $b} keys %{$self->get_exchanges->{$period}->{$self->argument}->{$col}}) {
				$timevariants->add_row("","",$period, $col, $row, $self->get_exchanges->{$period}->{$self->argument}->{$col}->{$row} );
			}
		}
	}
	$timevariants->add_row("","","", $self->delim("#END_CHANGING"));
	$self->add_block($timevariants);
	return $timevariants;
}
sub add_mpmas_seasonal_database {
	my ($self) = @_;
	my $seasonaltab;
	
    if( $self->config_entry("PERIODS_PER_YEAR")  > 1) {
    
    	my $NumItems = scalar @{$self->config_entry("SEASONAL_ITEMS_SORTED")};
    	my $NumRow = $self->milpinfo("NumRow");
    	my $NumCol = $self->milpinfo("NumCol");
    	my $numMilps = scalar @{ $self->milplist };
        
	    if ( ($NumItems > $self->config_entry("LIMIT_FOR_IMMEDIATE")  /2 /$numMilps ) 
	        || ( $NumRow   > $self->config_entry("LIMIT_FOR_IMMEDIATE")   /$numMilps ) 
		    || ( $NumCol   > $self->config_entry("LIMIT_FOR_IMMEDIATE")   /$numMilps ) 
		    ) {
		    $seasonaltab  = MpmasInputBlockImmediate->new(2,250, $self->get_skip_comments());
	    }
	    else {
        	$seasonaltab = MpmasInputBlock->new(2,250);
        }


	
	
		my $seasonalData = $self->config_entry("CONVERTED_SEASONAL_DATA");	
		my $itemIds = $self->config_entry("SEASONAL_ITEM_LOOKUP");
	
		
		$seasonaltab->add_empty_rows(3); 
		$seasonaltab->add_row("","Number of seasonal data entries", $NumItems); 
		
		my @seasons = ( 1 .. $self->config_entry("PERIODS_PER_YEAR")  );
		
		my $thismarket = substr($self->argument, -1);
		
		for my $item (@{$self->config_entry("SEASONAL_ITEMS_SORTED")}) {
			my @row = ($item, $itemIds->{$item},"");
					
			my $prev;
			for my $l (@seasons){
				my $y = $seasonalData->query([$item, $thismarket, $l], 4); 
				if (not defined $y and defined $prev) {
					print STDERR "Warning: missing seasonal entry $l for $item !\nUsed previous value for now!\n" ;
					$y = $prev;
				}
				elsif (not defined $y ) {
					print "\n";
					$seasonalData->printme_with_header_sorted_to_fh(*STDERR);
					print STDERR "Error: missing seasonal entry $l for $item !\n" ;
					exit(1);
				}
				$prev = $y;
				push @row, $y;
			}
			$seasonaltab->add_row(@row);
		}
	}
	else {
		$seasonaltab = MpmasInputBlock->new(2,250);
		$seasonaltab->add_empty_rows(3); 
		$seasonaltab->add_row("","Number of seasonal data entries", 0); 


	}
	$seasonaltab->add_row("","", $self->delim("#END_MPMAS_SEASONAL_DB"));
	$self->add_block($seasonaltab);	
}
sub add_mpmas_decision_stage_database {
	my ($self) = @_;
	my $seasonaltab;
	
	if( $self->config_entry("ADDITIONAL_DECISION_STAGES")  > 0) {

		my $NumItems = scalar @{$self->config_entry("STAGES_ITEMS_SORTED")};

    	my $NumRow = $self->milpinfo("NumRow");
    	my $NumCol = $self->milpinfo("NumCol");
    	my $numMilps = scalar @{ $self->milplist };


        if ( ($NumItems > $self->config_entry("LIMIT_FOR_IMMEDIATE")  /2 /$numMilps )
	        || ( $NumRow   > $self->config_entry("LIMIT_FOR_IMMEDIATE")   /$numMilps ) 
	        || ( $NumCol   > $self->config_entry("LIMIT_FOR_IMMEDIATE")   /$numMilps ) 
	        ) {
	        $seasonaltab  = MpmasInputBlockImmediate->new(2,260, $self->get_skip_comments());
        }
        else {
        	$seasonaltab = MpmasInputBlock->new(2,260);
        }


	

		my $seasonalData = $self->config_entry("CONVERTED_STAGES_DATA");	
		my $itemIds = $self->config_entry("STAGES_ITEM_LOOKUP");

		
		$seasonaltab->add_empty_rows(3); 
		$seasonaltab->add_row("","Number of decision stage data entries", $NumItems); 
		
		my @seasons = ( 0 .. $self->config_entry("ADDITIONAL_DECISION_STAGES")  );
		
		
		
		for my $item (@{$self->config_entry("STAGES_ITEMS_SORTED")}) {
			my @row = ($item, $itemIds->{$item},"");
					
			my $prev;
			for my $l (@seasons){
				my $y = $seasonalData->query([$item, $l], 3); 
				if (not defined $y and defined $prev) {
					print STDERR "Warning: missing decision stage-specific entry $l for $item !\nUsed previous value for now!\n" ;
					$y = $prev;
				}
				elsif (not defined $y ) {
					print "\n";
					$seasonalData->printme_with_header_sorted_to_fh(*STDERR);
					print STDERR "Error: missing decision stage-specific entry $l for $item !\n" ;
					exit(1);
				}
				$prev = $y;
				push @row, $y;
			}
			$seasonaltab->add_row(@row);
		}
	}
	else {
		$seasonaltab = MpmasInputBlock->new(2,260);
		$seasonaltab->add_empty_rows(3); 
		$seasonaltab->add_row("","Number of decision stage data entries", 0); 


	}
	$seasonaltab->add_row("","", $self->delim("#END_MPMAS_STAGE_DB"));
	$self->add_block($seasonaltab);	
}


sub add_tableau { # overload MILPAbstract to allow for lazy evaluation of coefficients
	my ($self, $singlemode, $curRHS, $rhshash) = @_;
	my $matrixtableau2 = MpmasInputBlock->new(9,34);
	my $NumRow = $self->milpinfo("NumRow");
	my $NumCol = $self->milpinfo("NumCol");
	my $numMilps = scalar @{ $self->milplist };

	my $digs = $self->milpinfo("Digs");
	my @empty;
	$#empty = 101;
	@empty = ("") x @empty;	

	my $matrixcoefficients;
	if ($NumCol > $self->config_entry("LIMIT_FOR_IMMEDIATE") / $numMilps or $NumRow > $self->config_entry("LIMIT_FOR_IMMEDIATE") / $numMilps) {
		$matrixcoefficients = MpmasInputBlockImmediate->new(9,35, $self->get_skip_comments());
	}
	else {
		$matrixcoefficients = MpmasInputBlock->new(9,35);
	}
	$matrixcoefficients->add_empty_rows(2);
	$matrixcoefficients->add_comment_row("3 B", "Matrix coefficients");
	$matrixcoefficients->add_empty_rows(1);

	$matrixcoefficients->add_comment_row(@empty[0..6], "Constraint", "Activity", "RowId", "ColumnId", "Coefficient");

	my $count_coef = 0;
	my $mu = MpmasInputfile->getMemUsageObj();
	#$mu->record("before looping through coefficients") if logMem;
    if (MpmasMILPConstraint->lazy_constraint()) {
        foreach my $x (@{$self->sorted_constraint_objects()} ){

		    #next if $x->empty;
		    my $row = $x->lp_row;
            my ($fields,$newinserters) = $x->transform_coefficients($self->setup(), $self->get_activity_registry() );        
            #print "Coefficients : ",$x->id(),  Dumper($fields), "\n";
	        $mu->record("after transform_coefficients for " .$x->id()) if logMem;
            for my $rowelement (sort { $a cmp $b} keys %{ $fields->{coefficients}}){
	                my $act = $self->activity($rowelement);
	                print STDERR "Error: ",$x->id(),"\t$rowelement\t".$act->id."\t".$fields->{coefficients}->{$rowelement}."\n" and exit(1) if ($fields->{coefficients}->{$rowelement}   =~ /[^\s\d.+Ee-]/);
	                $matrixcoefficients->add_row(@empty[0..6],$x->id, $act->id, $row, $act->lp_column(), sprintf("%e", $fields->{coefficients}->{$rowelement})  ) ;
	                $count_coef++;
	                
            }
  	 #       $mu->record("after printing fixed coefficients for " .$x->id()) if logMem;
            my $ir = $self->get_inserter_registry();
            #print "Second:", $ir,"\n";
            #print "NI: \n", Dumper($newinserters);

            
	        for my $iFunc ( 0.. $#{$newinserters}) {
	        		            
		        for my $iInfo ( @{ $newinserters->[$iFunc] } ) {

		            if (exists $iInfo->{at}) {
		                my $c;
		                eval {
    		            	$c = $self->lp_column_of_activity($iInfo->{at});
    		            };
	    				print STDERR "Error: Unknown activity '$iInfo->{at}' referenced in inserter in subelement of constraint". $iInfo->{co}->id()."\n" and exit(1) if ($@);
                        delete $iInfo->{at};
						$iInfo->{col}=  $c;
						

		            }
		            unless (defined ${$ir}->[$iFunc]) {
    		            ${$ir}->[$iFunc] = [];
		            }
			        push @{ ${$ir}->[$iFunc] }, $iInfo;
		        }
	        }
	        #$mu->record("after storing inserters for " .$x->id()) if logMem;
	        #$self->set_inserter_registry($ir);
	        
	        delete $fields->{coefficients};
	        undef %$fields;
		}
    } 
    else {
        	foreach my $co ($self->sorted_constraints() ){
		        my $x = $self->constraint($co);
		        #next if $x->empty;
		        my $row = $x->lp_row;
		        for my $rowelement (sort { $a cmp $b} $x->coefficients){
			            my $act = $self->activity($rowelement);
			            print STDERR "Error: Activity $rowelement of constraint $co is not declared anywhere in class file.\n" and exit(1) unless defined $act; 
			            next if $act->empty;
			            print STDERR "Error: $co\t $rowelement \t".$act->id."\t".$x->coeff($rowelement, $self->argument)."\n" and exit(1) if ($x->coeff($rowelement, $self->argument) =~ /[^\s\d.+Ee-]/);
			            #if (  $x->coeff($rowelement, $self->argument) != 0)  { 
			            #Note: We explicitly keep zero coefficients that were given as explicit zero in the .mpm file. These usually come from
			            # places were mpmas inserters have been used in the MULTIPERIOD version. Keeping them in the list allows us
			            # to use them as placeholders for the values that will later be inserted
			            # MPMAS uses as sparse Matrix format and otherwise it needs to reallocate and shift
			            # the whole matrix whenever a coefficient has to be inserted that was previously zero.
			            $matrixcoefficients->add_row(@empty[0..6],$co, $act->id, $row, $act->lp_column(), sprintf("%e", $x->coeff($rowelement, $self->argument))  ) ;
			            $count_coef++;
			            #}
		        }
		}

		
	}


	
	$matrixtableau2->add_row(@empty[0..7],"Number of Coefficients:",$count_coef ) ;#if ($self->tableau_compact);
	$matrixtableau2->add_row(@empty[0..9],  $self->delim("#END_NUMTRIPLETS"));

	$matrixcoefficients->add_row(@empty[0..9],  $self->delim("#END_BLOCK"));
	$self->add_block($matrixtableau2);
	$self->add_block($matrixcoefficients);
	return;

}

sub calculate_milpinfo {
	my ($self) = @_;
	my $NumRow = $self->number_of_constraints;
	my $NumCol = $self->number_of_activities;

	my $IntegAct = 0;
	my $QuadraticAct = 0;
	my ($ZeroCon, $ZeroAct) = (0,0);
	my $exchanges = {};
	my $number_of_exchanges = {};
	for my $milpid (@{$self->milplist}) {
			$number_of_exchanges->{$milpid} = 0;
	}
	my @PeriodStartCols = (0);
	my @PeriodStartRows = (0);
	my $curMPeriod = 0;
	
    for my $t (@{$self->sorted_constraint_objects}){
	    while ($curMPeriod < $t->attr("mperiod")) {
		    $curMPeriod += 1;
		    push @PeriodStartRows, $t->lp_row();
	    }			    
    }
	    
	my @SortedQuadAct;
	$curMPeriod = 0;
	for my $t (@{$self->sorted_activity_objects} ){

		#$ZeroAct +=  1 if (lc($t->attr("type")) eq "maszero" or lc($t->attr("type")) eq "masfuture"  or lc($t->attr("type")) eq "masxc_inc_trans");
		eval {   
			 use warnings FATAL => qw/uninitialized numeric/;
			 $IntegAct += 1	if ($t->attr_defined("integ") && $t->attr("integ") == 1) 
		};
		print STDERR "Error in integer attribute of activity ".$t->id.":\n". MpmasInputfile::shortError($@)."\n" and exit(1) if $@;
		#print $t->attr("name")."\n" if (!$t->attr_defined("integ") ) ;
	      
		#identify quadratic activities
		if ($t->attr_defined("quadratic") && $t->attr("quadratic") == 1) {
		    $QuadraticAct += 1 ;
		    push @SortedQuadAct, $t->id;
		}
	
	      eval {   
		       use warnings FATAL => qw/uninitialized numeric/;		
		
			while ($curMPeriod < $t->attr("mperiod")) {
				$curMPeriod += 1;
				push @PeriodStartCols, $t->lp_column();
			}
		 };
		 print STDERR "Error in mperiod attribute of activity ".$t->id.":\n". MpmasInputfile::shortError($@)."\n" and exit(1) if $@;
		
	}
	$self->set_sorted_quadratic_activities(\@SortedQuadAct);
	$self->set_exchanges($exchanges);
	$self->set_number_of_exchanges($number_of_exchanges);


	print "\e[K \rMILP".$self->file_suffix() .":\n";
	print "\tNumber of rows:\t\t $NumRow \n";
	print "\tNumber of columns:\t $NumCol \n";
	#print "\tNumber of nonzero constraints:\t ".($NumRow - $ZeroCon)." \n" unless ($self->config_entry("MPMASCHECKSOFF"));
	#print "\tNumber of nonzero activities:\t ".($NumCol -$ZeroAct)." \n" unless ($self->config_entry("MPMASCHECKSOFF"));
	print "\tNumber of integers:\t $IntegAct \n\n";
	my $milpinfo = {(
				NumCol => $NumCol,
				NumRow => $NumRow,
				ZeroAct => $ZeroAct,
				ZeroCon => $ZeroCon,		
				IntegAct => $IntegAct,
				QuadraticAct => $QuadraticAct,
				Digs	=> $self->config_entry("DIGS"),
				PeriodStartCols => \@PeriodStartCols,
				PeriodStartRows => \@PeriodStartRows
	)};
	$self->set_milpinfo($milpinfo);
}
1;
