More on economic model

[From Bill Powers (2002.01.18.1233 MST)]

The Turbo Pascal 7 source program appended is a start toward an economic
model with multiple plants, multiple goods, and multiple households. It
doesn't actually get as far as being a running model. The main point is to
set up data records, and to show how a system can be initialized to embody
a range of preferences and characteristics of the physical parts of the
system. This version is set up for 20 plants, 20 types of goods, and 150
households. Nothing keeps the numbers from being larger but the fact that
in TP7, memory can be reserved in chunks only up to 64K bytes in size, and
the total heap size is limited as well. If ported into a protected-mode
language like Delphi or DJGPP, the numbers could be a hundred times larger
or even more. The four data records occupy 159520 bytes.

I did this partly to see what would be involved in just using ordinary
"records" to hold the data instead of the fancier methods I was using
earlier. Records are much handier than arrays because they can hold data of
different types. The awkward part is doing things like editing records,
where each field has to be given a displayable name, and since the fields
are not elements of an array, each field has to be dealt with individually.
My method of using arrays saved a lot of typing of source code.

The initialization is done in four procedures which initialize the records
for consumer and household, and for manager and plant.

In this initialization, the consumers are set up with reference levels for
the amount of each type of good, and control-system gains for each type
which determine how much effort will be put out to correct errors in the
amount of goods obtained. The reference levels and gains are randomly
distributed over a range. Consumers also have a reference level for how
much is to be maintained in savings, and a gain to go with it.

The households that the consumers run are initialized with fields showing
which plants supply each desired good, how much of each good is on hand,
the depreciation and usage rates for those goods, and the number being
bought per day. Also given for each household are the income and
expenditure per day, number of people in the household, number of employed
persons, and the (one) plant employing them. Most of the numbers that are
initialized are randomized over a range.

Similar initializations are given for the managers and the plants they run.
There are lots of comments to help identify the variables and constants, so
it is possible to learn the organization of the program with a little study.

The program is set up in text mode, and it runs in the sense of reserving
memory storage, reading data files if they exist, initializing all the
records, saving the data files again, and releasing the reserved memory.
There are a couple of print statements in the household initialization,
which print out for each household the plant number where each of the 20
goods is obtainable.

The plant is initialized first to assure that each good is obtainable from
1 to 4 plants (selected at random); this must be done before the households
can be initialized. Household initialization uses a function that randomly
searches throught the plants to find a source for each good. Later, there
might be a higher-level consumer control system that continually uses some
specific strategy to decide which source for a given good to use -- the
cheapest one, for example.

With a very large simulation, the starting values of parameters and
variables have to be program-generated according to general rules. The
nature of these rules determines important characteristics of the system,
so one aim in of using the model will be to see the effects of different
initializations, given the same consumer and manager control systems. As
you study this program, you will probably see alternatives to the
particular initializations I have chosen, if "choose" isn't too much of an
exaggeration. Of course we will also want to see the effects of different
consumer and manager strategies for a given initialization of plant and
household.

I've put in some very simple control systems for consumers and managers.
Before the system will be runnable, these have to be made more complete,
and information that is scattered over multiple systems has to be gathered
into usable form -- for example, the total amount of wages earned by
workers making each of the different goods. This number is used to
calculate how much capital income is to be distributed, since capital and
wage incomes are in a fixed ratio. That illustrates another point where
alternative hypotheses can be made.

I invite others to think critically about chmp007.pas, and add or change
whatever they wish. I do ask that every alternative offered be set up so it
will compile and run (even if it doesn't actually do anything useful),
because that's the only way to make sure there are no definitions omitted,
no multiple definitions, and no loose ends in general. Each person should
set up a personal naming convention, and stick to it, so we will not get
versions mixed up. We'll probably get them mixed up anyway, but we should
put up at least token resistance to chaos.

Questions aqnd discussion are invited.

Best,

Bill P.

···

==========================================================================
program CHMP007; { Consumer, household, manager, and plant }

{
New version using record variables. Initializations only. 2001.01.17 WTP
}

uses dos, crt, graph, setsvga{,grutils};

const wbar = 24;
      backgrnd = black;
      lettcolor = white;
      numplants = 20;
      numgoods = 20;
      numhouseholds = 150;
      up = 'H'; {'H' & 'P' purely a coincidence!}
      down = 'P'; { ch2 value for each special key}
      right = 'M'; { when ch1 = #0}
      left = 'K';
      pgUp = 'I';
      pgDn = 'Q';
      Endkey = 'O';
      Home = 'G';

type ctype = record { consumer control system variables, parameters}
              cSr, {Savings reference level}
              cSg, {Savings control gain}
              cSo: {Savings control output}
                   double;
              cAr, {Acquisitions reference level, each good}
              cAg, {Acquisitions control gain, each good}
              cAo: {Acquisitions control output, each good}
                  array[1..numgoods] of double;
             end;

     goodsettype = set of 1..numgoods;

     cgoodtype = record {data applying to each good}
                 Sup{plier}: integer; {number of plant}
                 Acq{uired}: integer; {number of Acquired items on hand}
                 Dep{rec}: double; {fraction per day}
                 Use: integer; {number used per day}
                 Buy: integer; {number bought per day}
                end;

     htype = record {data, each household}
              hG: array[1..numgoods] of cgoodtype;
              hS, {savings/checking, dollars}
              hX, {Expenses, $/day}
              hY: {Income, $/day}
                  double;
              hNp, {Num of people in this household}
              hNw, {Num of income recipients in this household}
              hE: {Employer, plant number}
                  word;
             end;

     mtype = record {manager control system variables, parameters}
              mIr, {Investment fund reference level, $}
              mIg, {Investment fund control gain}
              mIo: {Investment fund control output}
                   double;
              mVr, {Inventory reference level, each good}
              mVg, {Inventory control gain, each good}
              mVo: {Inventory control output}
                   array[1..numgoods] of double;
             end;

     pgoodtype = record {for each kind of good}
                  pV, {Inventory, number of unsold goods}
                  pP, {Price of good, $/good}
                  pDv, {Inventory depreciation, fract/day}
                  pDm, {Machinery depreciation, frac/day}
                  pC: {Productivity, number/day/worker}
                      double;
                  pMw: integer; {number of workstations}
                  pMc: double; {cost per workstation}
                 end;

     ptype = Record {for each plant}
              pGi: goodsettype;
              pG: array[1..numgoods] of pgoodtype;
              pR, {Cash reserves, $}
              pI: {Investment fund, $}
                  double; {cumulative variables}
              pO, {Plant output, goods/day}
              pK, {Capital distributions, $/day}
              pY, {Plant income, $/day}
              pX: {Plant expenses, $/day}
                  double; {rate variables}
              pW, {Wages, $/day/worker}
              pKp: {Capital income percent of wages,$/day/recipient}
                  double; {constant parameters}
             end;

var c: array[1..numhouseholds] of ^ctype;
     h: array[1..numhouseholds] of ^htype;
     m: array[1..numplants] of ^mtype;
     p: array[1..numplants] of ^ptype;

     cfile: file of ctype;
     hfile: file of htype;
     mfile: file of mtype;
     pfile: file of ptype;

     dt: double;
     i: integer;
     ch: char;
     numstr: string;
     ch1,ch2: char;

{The four types of data records are stored in memory reserved on the heap.
Data records are referenced by arrays of pointers c[i]^.. p[i]^.}

procedure reservememory;
var i: integer;
begin
for i := 1 to numhouseholds do
begin
  getmem(h[i], sizeof(htype));
  getmem(c[i], sizeof(ctype));
end;
for i := 1 to numplants do
begin
  getmem(p[i], sizeof(ptype));
  getmem(m[i], sizeof(mtype));
end;
end;

procedure freememory;
var i: integer;
begin
for i := 1 to numhouseholds do
begin
  freemem(h[i], sizeof(htype));
  freemem(c[i], sizeof(ctype));
end;
for i := 1 to numplants do
begin
  freemem(p[i], sizeof(ptype));
  freemem(m[i], sizeof(mtype));
end;
end;

procedure getcmd; {for cases when special keys are used}
begin
ch1 := readkey;
if ch1 = #0 then
   ch2 := readkey
else ch2 := #0;
end;

procedure saveconsumer;
var i: integer;
begin
assign(cfile,'consumer.dat');
rewrite(cfile);
for i := 1 to numhouseholds do
  write(cfile,c[i]^);
close(cfile);
end;

procedure savehousehold;
var i: integer;
begin
assign(hfile,'househld.dat');
rewrite(hfile);
for i := 1 to numhouseholds do
  write(hfile,h[i]^);
close(hfile);
end;

procedure savemanager;
var i: integer;
begin
assign(mfile,'manager.dat');
rewrite(mfile);
for i := 1 to numplants do
  write(mfile,m[i]^);
close(mfile);
end;

procedure saveplant;
var i: integer;
begin
assign(pfile,'plant.dat');
rewrite(pfile);
for i := 1 to numplants do
  write(pfile,p[i]^);
close(pfile);
end;

procedure initconsumer;
var i,j: integer;
begin
for i := 1 to numhouseholds do
with c[i]^ do
  begin
   for j := 1 to numgoods do
    begin
     cAr[j] := 1000.0 + 100*random; {Set ref levels for all goods}
     cAg[j] := 0.01 * cAr[j]; {higher ref level also more important}
     cAo[j] := 0.0;
    end;
   cSr := 500.0 + random*1000.0;
   cSg := 0.01;
   cSo := 0.0;
end;
end;

{NOTE: I'VE PULLED DOWN COPIES OF RECORD DEFINITIONS FOR EASE OF REFERENCE}
(* cgoodtype = record {data applying to each good}
                 Sup{plier}: integer; {number of plant}
                 Acq{uired}: integer; {number of Acquired items on hand}
                 Dep{rec}: double; {fraction per day}
                 Use: integer; {number used per day}
                 Buy: integer; {number bought per day}
                end; *)

function findplant(n: integer): word; {find a plant that makes nth good}
var i: integer;
begin
repeat
  i := random(word(numplants)) + 1; {Pick a plant at random}
until n in p[i]^.pGi; { find a plant that makes the good}
findplant := i;
end;

(* htype = record {data, each household}
              hG: array[1..numgoods] of cgoodtype;
              hS, {savings/checking, dollars}
              hX, {Expenses, $/day}
              hY: {Income, $/day}
                  double;
              hNp, {Num of people in this household}
              hNw, {Num of income recipients in this household}
              hE: {Employer, plant number}
                  word;
             end; *)

procedure inithousehold;
var i,j,k: integer;
begin
for i := 1 to numhouseholds do
with h[i]^ do
begin
  writeln;
  for j := 1 to numgoods do
  with hG[j] do
   begin
    Sup := findPlant(j);
    write(Sup:2,' ');
    Acq := 100;
    Dep := 0.01;
    Use := random(round(c[i]^.cAr[j] / 10.0));
        {up to 10% of ref amount per day}
   end;
end;
end;

(* mtype = record {manager control system variables, parameters}
              mIr, {Investment fund reference level, $}
              mIg, {Investment fund control gain}
              mIo, {Investment fund control output}
              mVr, {Inventory reference level, one good}
              mVg, {Inventory control gain}
              mVo: {Inventory control output}
                   array[1..numgoods] of double;
             end; *)

procedure initmanager;
var i,j: integer;
begin
for i := 1 to numplants do
with m[i]^ do
begin
  mIr := 10000.0;
  mIg := 1.0;
  for j := 1 to numgoods do
  with p[i]^ do
  if j in pGi then
   mVr[j] := 200 + random(300)
  else mVr[j] := 0;
end;
end;

(* ptype = Record {for each plant}
              pGi: goodsettype;
              pG: array[1..numgoods] of pgoodtype;
              pR, {Cash reserves, $}
              pI: {Investment fund, $}
                  double; {cumulative variables}
              pO, {Plant output, goods/day}
              pK, {Capital distributions, $/day}
              pY, {Plant income, $/day}
              pX: {Plant expenses, $/day}
                  double; {rate variables}
              pKp: {Capital income percent of distributions}
                  double; {constant parameters}
             end; *)

(* pgoodtype = record {for each kind of good}
                  pV, {Inventory, number of unsold goods}
                  pP, {Price of good, $/good}
                  pDv, {Inventory depreciation, fract/day}
                  pDm, {Machinery depreciation, frac/day}
                  pC: {Productivity, number/day/worker}
                      double;
                  pW, {Wages, $/day/worker making this good}
                  pMw: integer; {number of workstations}
                  pMc: double; {cost per workstation}
                 end;*)

procedure initplant;
var i,k: integer;
    j: byte;
begin
for i := 1 to numplants do
with p[i]^ do
  fillchar(p[i]^,sizeof(ptype),0);

for j := 1 to numgoods do
for k := 1 to 1+random(4) do {each good in up to 4 plants}
begin
  i := 1 + random(numplants);
  with p[i]^ do
   pGi := pGi + [j]; { add good j to plant i}
end;

for i := 1 to numplants do
with p[i]^ do
  begin
   for j := 1 to numgoods do
   with pG[j] do
   if j in pGi then
   begin
    pV := 10 + random(100);
    pP := 1.0 + 10.0*random;
    pDv := 0.001 + random*0.02;
    pDm := 0.001 + random*0.02;
    pC := 200.0 + random*200.0;
    pW := 100 + 300*random;
    pMw := 1 + random(10);
    pMc := 1000.0 + 1000.0*random;
   end;
   pR := 1000.0;
   pI := 1000.0;
   pKp := 0.6; { capital income = % distributions}
  end;
end;

procedure restoreconsumer;
var i,j: integer;
begin
assign(cfile,'consumer.dat');
{$i-}
i := 0;
reset(cfile);
{$i+}
if IOresult = 0 then
begin
  while (not eof(cfile)) and (i < numhouseholds) do
   begin
    read(cfile,c[i+1]^);
    inc(i)
   end;
  close(cfile);
end;
while i < numhouseholds do {clear any unfilled arrays}
  begin
   fillchar(c[i+1]^,sizeof(ctype),0);
   inc(i);
  end;
end;

procedure restorehousehold;
var i,j: integer;
begin
assign(hfile,'househld.dat');
{$i-}
i := 0;
reset(hfile);
{$i+}
if IOresult <> 0 then inithousehold
else
begin
  while (not eof(hfile)) and (i < numhouseholds) do
   begin
    {$i-}
    read(hfile,h[i+1]^);
    {$i+}
    inc(i);
    if IOResult <> 0 then halt; {Format has changed}
   end;
  close(hfile);
  while i < numhouseholds do
  begin
   fillchar(h[i+1]^,sizeof(htype),0);
   inc(i);
  end;
end;
end;

procedure restoremanager;
var i,j: integer;
begin
assign(mfile,'manager.dat');
{$i-}
i := 0;
reset(mfile);
{$i+}
if IOresult <> 0 then initmanager
else
begin
  while (not eof(mfile)) and (i < numplants) do
   begin
    read(mfile,m[i+1]^);
    inc(i)
   end;
  close(mfile);
  while i < numplants do
  begin
   fillchar(m[i+1]^,sizeof(mtype),0);
   inc(i);
  end;
end;
end;

procedure restoreplant;
var i,j: integer;
begin
assign(pfile,'plant.dat');
{$i-}
i := 0;
reset(pfile);
{$i+}
if IOresult <> 0 then initplant
else
begin
  while (not eof(pfile)) and (i < numplants) do
   begin
    read(pfile,p[i+1]^);
    inc(i)
   end;
  close(pfile);
  while i < numplants do {if more plants added, zero them out}
  begin
   fillchar(p[i+1]^,sizeof(ptype),0);
   inc(i);
  end;
end;
end;

procedure initprogram;
begin
end;

begin
clrscr;
{ initsvga(3);} {No graphics yet}
reservememory;
randomize;
initprogram;
initplant; {must be done first}
initconsumer;
inithousehold;
initmanager;
writeln('All initialized');
writeln('One Row/household. Horiz entries = plant #, in order of good #');
{program runs here}
saveconsumer;
savehousehold;
savemanager;
saveplant;
freememory;
{closegraph;}
end.