Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

книги / Моделирование и оптимизация в LINGO

..pdf
Скачиваний:
10
Добавлен:
12.11.2023
Размер:
2.55 Mб
Скачать

манду Project|Add to Project|Files и выбираем файл LINGO16.LIB в каталоге \LINGO16\Programming Samples.

Рис. 26

Для добавления в проект определений подпрограмм LINGO DLL помещаем файл Lingd16.h в верхней части заголовочного файла диалогового класса, как показано ниже:

// staffDlg.h : header file

//

#include "lingd16.h"

#if !de-

fined(AFX_STAFFDLG_H__74D746B7_CA4D_11D6_AC89_00010240D2AE__ INCLUDED_)

#define AFX_STAFFDLG_H__74D746B7_CA4D_11D6_AC89_00010240D2AE__INCLUDED_ #if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

////////////////////////////////////////////////////////////

/////////////////

// CStaffDlg dialog

111

Теперь мы можем программировать вызов LINGO для решения нашей задачи. Для этого перейдем в Resource View проекта и откроем ресурс диалогового окна. Затем дважды щелкнем по кнопке Решение, чтобы создать функцию обработчика для кнопки, которую именуем OnSolve. С этой целью, используя функции DLL, отредактируем заглушку версии OnSolve. В результате получаем нижеследующий код, в котором жирным шрифтом выделены импортируемые функции DLL:

1.___________________________________________________

void CStaffDlg::OnSolve()

{

int nError, nPointersNow; CString csScript, cs;

double dNeeds[7], dStart[7], dOnDuty[7], dStatus, dTotal;

//Get user's staffing requirements from our dialog box UpdateData();

//Load staffing requirements into the LINGO transfer array.

//LINGO uses double precision for all values. dNeeds[ 0] = (double) m_nNeedsMon;

dNeeds[ 1] = (double) m_nNeedsTue; dNeeds[ 2] = (double) m_nNeedsWed; dNeeds[ 3] = (double) m_nNeedsThu; dNeeds[ 4] = (double) m_nNeedsFri; dNeeds[ 5] = (double) m_nNeedsSat; dNeeds[ 6] = (double) m_nNeedsSun;

2.___________________________________________________

// create the LINGO environment object pLSenvLINGO pLINGO;

pLINGO = LScreateEnvLng(); if ( !pLINGO)

{

AfxMessageBox("Unable to create LINGO Environment"); return;

}

3.___________________________________________________

// Open LINGO's log file

nError = LSopenLogFileLng( pLINGO, "LINGO.log"); if ( nError) goto ErrorExit;

4.___________________________________________________

//Pass memory transfer pointers to LINGO

//@POINTER(1)

112

nError = LSsetPointerLng( pLINGO, dNeeds, &nPointersNow); if ( nError) goto ErrorExit;

// @POINTER(2)

nError = LSsetPointerLng( pLINGO, dStart, &nPointersNow); if ( nError) goto ErrorExit;

// @POINTER(3)

nError = LSsetPointerLng( pLINGO, dOnDuty, &nPointersNow); if ( nError) goto ErrorExit;

// @POINTER(4)

nError = LSsetPointerLng( pLINGO, &dTotal, &nPointersNow); if ( nError) goto ErrorExit;

// @POINTER(5)

nError = LSsetPointerLng( pLINGO, &dStatus, &nPointersNow); if ( nError) goto ErrorExit;

5.___________________________________________________

//Here is the script we want LINGO to run csScript = "SET ECHOIN 1\n";

csScript = csScript +

"TAKE \\LINGO16\\SAMPLES\\STAFFPTR.LNG\n"; csScript = csScript +

"GO\n";

csScript = csScript + "QUIT\n";

//Run the script

dStatus = -1.e0;

nError = LSexecuteScriptLng( pLINGO, (LPCTSTR) csScript); 6.___________________________________________________

//Close the log file

LScloseLogFileLng( pLINGO);

//Any problems?

if ( nError || dStatus) // Had a problem

AfxMessageBox("Unable to solve!"); } else

// Everything went ok... load results into the dialog box

m_csStartMon.Format( "%d", (int) dStart[0]); m_csStartTue.Format( "%d", (int) dStart[1]); m_csStartWed.Format( "%d", (int) dStart[2]); m_csStartThu.Format( "%d", (int) dStart[3]); m_csStartFri.Format( "%d", (int) dStart[4]); m_csStartSat.Format( "%d", (int) dStart[5]); m_csStartSun.Format( "%d", (int) dStart[6]); m_csOnDutyMon.Format( "%d", (int) dOnDuty[0]); m_csOnDutyTue.Format( "%d", (int) dOnDuty[1]); m_csOnDutyWed.Format( "%d", (int) dOnDuty[2]); m_csOnDutyThu.Format( "%d", (int) dOnDuty[3]);

113

m_csOnDutyFri.Format( "%d", (int) dOnDuty[4]); m_csOnDutySat.Format( "%d", (int) dOnDuty[5]); m_csOnDutySun.Format( "%d", (int) dOnDuty[6]); m_csCost.Format( "%g", dTotal);

UpdateData( FALSE);

}

goto Exit; ErrorExit:

cs.Format( "LINGO Errorcode: %d", nError); AfxMessageBox( cs);

return; 7.___________________________________________________

Exit:

LSdeleteEnvLng( pLINGO);

}

Кратко поясним части кода.

В1-й части устанавливаются требования ко всем атрибутам как значениям с двойной точностью. В таком формате потребности (Needs) извлекаются из диалогового окна и загружаются в массив передачи LINGO, так как LINGO пересылает данные только в формате двойной точности.

Далее следует код первого обращения к LINGO, которым создается объект среды LINGO. Заметим, что используемый здесь тип данных pLSenvLINGO определен в заголовочном файле lingd16.h.

Следующий шаг – открытие лог-файла LINGO (часть 3).

В4-й части определяются все указатели памяти, через которую будет происходить передача данных и решения.

5-я часть содержит скрипт, в котором командой TAKE загружается наша модель. Затем скрипт запускается в LINGO.

В6-й части приведен код закрытия лог-файла, проверки на разрешимость модели и определения форматов передачи результатов.

На последнем шаге освобождается задействованная приложением память.

Теперь выбираем команду построения проекта, после чего запускаем проект, что приводит к появлению интерфейса приложения

(рис. 27).

114

Рис. 27

Вводим потребности по каждому дню недели (рис. 28).

Рис. 28

Нажав кнопку Решение, получаем результаты решения

(рис. 29).

115

Рис. 29

8.4.2. Передача нечисловых данных

Выше рассматривалось применение функции @POINTER при экспорте и импорте значений атрибутов. Однако она также может применяться для передачи нечисловых данных, в частности списка элементов множеств. При этом в модели левее или правее функции записывается не атрибут, а имя множества. Принципиальное отличие состоит в представлении данных. В случае с множествами список элементов множества передается как текст ASCII. Кроме того, каждый элемент списка разделяется символами перевода строки (ASCII 10), а конец списка элементов обозначается символом NULL (ASCII 0).

Для пояснения рассмотрим фрагменты некоторой модели. Пусть в разделе SETS мы имеем

SETS :

ITEMS: WEIGHT, UTILITY, X; ENDSETS

116

где определено множество предметов (ITEMS) с атрибутами вес, полезность и X (переменная, означающая действия брать или не брать предмет).

В разделе DATA функции @POINTER используются для передачи данных из приложения в LINGO:

DATA:

ITEMS = @POINTER(1);

WEIGHT = @POINTER(2);

UTILITY = @POINTER(3); WEIGHT_RESTRICTION = @POINTER(4);

ENDDATA

Здесь наряду с числовыми данными импортируются и элементы множества ITEMS.

В коде приложения на языке программирования C исходные данные представлены следующим фрагментом:

//множество предметов для выбора

char pcItems[256] = "ХЛЕБ \n КОНСЕРВЫ \n ПИВО \n" "ВОДА \n СЫР \n СУХАРИ \n СОЛЬ \n МЫЛО";

//вес предметов

double pdItemWeight[8] = { 2, 3, 1, 3, 1, 1, 1, 1};

// полезность предметов

double pdItemRank[8] = { 3, 10, 1, 4, 6, 9, 7, 5};

// допустимый вес выбранных предметов double dSackSize = 25;

Вэтом примере элементы множества ITEMS перечисляются

водной длинной текстовой строке, разделенной обозначениями перевода строки в C (\ n). При этом пробелы между элементами при считывании не учитываются. Заметим, что в конце строки элементов имеется неявный нулевой байт из-за использования двойных кавычек, по нему LINGO определяет конец списка.

Вэтом же коде передача списка указателей в память LINGO осуществляется вызовом процедуры LSsetPointerLng. Так, для 1-го указателя имеем

117

// @POINTER (1) – Набор элементов

nError = LSsetPointerLng (pLINGO, (void *) pcItems, & NPointersNow);

if (nError) goto ErrorExit;

Аналогично для других указателей.

Множество предметов, попавших в оптимальное решение, может быть образовано как производное от исходного множества

ITEMSOPT( ITEMS) | X(&1) #GT#.9:;

и затем передано в приложение

@POINTER( 5) = ITEMSUSED;

Примеры, приведенные в последних разделах, показали, как с помощью функции @POINTER можно передавать из приложения в LINGO и обратно атрибуты множеств (числа в формате двойной точности) и список элементов множеств (в виде текстовой строки).

Завершая рассмотрение использования LINGO в приложениях, поясним кратко еще одну возможность, предоставляемую LINGO DLL.

Решение некоторых задач может занимать много времени, и в таких случаях пользователь приложения хотел бы получать информацию о ходе решения. В интерактивной версии LINGO эту информацию можно отслеживать в выводимом при каждом вызове решателя окне состояния (статуса) процесса. Подобное диалоговое окно можно создать в приложении, привязав его к так называемой функции обратного вызова (CallBack Functions). Такое название обусловлено тем, что код этой функции вызывает из приложения решатель LINGO, а он затем делает обратный вызов подпрограммы CallBack, которая выводит нужную информацию в спроектированное диалоговое окно. В процессе решения задачи решатель будет часто вызывать функцию обратного вызова.

Для создания такой подпрограммы используются соответствующие функции, экcпортируемые LINGO DLL. Так, чтобы определить функцию обратного вызова, необходимо перед вызовом процессора обработки скриптов вызвать экспортированную процедуру

LSetCallBackSolverLng().

118

Все процедуры, используемые для создания функции обратного вызова, с краткими пояснениями приведены в приложении 5. Порядок создания такой функции и подробно разобранные примеры

вС++ и Visual Basic можно найти в руководстве [17, с. 584–594].

8.4.3.Функция пользователя

ВLINGO имеется функция @USER, функциональность которой задает пользователь. С этой целью на платформе Windows он создает собственную библиотеку DLL, которая содержит функцию @USER. DLL должна иметь имя MYUSER.DLL и располагаться

вглавном каталоге LINGO. При каждом запуске LINGO ищется эта DLL. Если она обнаружена, выдается сообщение о том, что она инсталлирована и, следовательно, функция @USER становится доступной.

Вмодели такая функция может иметь любое число аргументов, но не менее одного. Она возвращает результат, вычисляемый по программе, написанной пользователем (в DLL). С позиции же программиста функция @USER имеет только 2 входных аргумента: число аргументов и вектор, содержащий их значения.

Приведем пример создания DLL пользователя в Visual C++.

Всреде Microsoft Visual Studio в меню File/New выбираем Project (рис. 30).

Затем выделяем тип проекта MEC DLL, вводим имя проекта gol и каталог его размещения, нажимаем ОК (рис. 31).

Вэтом окне отмечено создание регулярной DLL, и для создания основы (скелетона) нашей библиотеки достаточно нажать ОК. Но мы выберем Next, чтобы еще раз увидеть, что создаем нужный нам тип DLL. В результате появляется окно, представленное на рис. 32.

Убедившись в правильности выбора, нажатием кнопки Finish получаем код основы нашей DLL. Затем редактируем его, добавляя часть кода, реализующую @USER как функцию вычисления разности абсолютного значения числа и его целой части. Добавленная часть кода видна в приведенном окне (рис. 33).

119

Рис. 30

Рис. 31

120

Соседние файлы в папке книги