changeDictionary.C 20.3 KB
Newer Older
mattijs's avatar
mattijs committed
1 2 3 4
/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     |
OpenFOAM bot's avatar
OpenFOAM bot committed
5
    \\  /    A nd           | www.openfoam.com
OpenFOAM bot's avatar
OpenFOAM bot committed
6 7
     \\/     M anipulation  |
-------------------------------------------------------------------------------
OpenFOAM bot's avatar
OpenFOAM bot committed
8
    Copyright (C) 2011-2016 OpenFOAM Foundation
9
    Copyright (C) 2016-2020 OpenCFD Ltd.
mattijs's avatar
mattijs committed
10 11 12 13
-------------------------------------------------------------------------------
License
    This file is part of OpenFOAM.

14 15 16 17
    OpenFOAM is free software: you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
mattijs's avatar
mattijs committed
18 19 20 21 22 23 24

    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    for more details.

    You should have received a copy of the GNU General Public License
25
    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
mattijs's avatar
mattijs committed
26

henry's avatar
henry committed
27 28 29
Application
    changeDictionary

30 31 32
Group
    grpPreProcessingUtilities

mattijs's avatar
mattijs committed
33
Description
34 35 36 37
    Utility to change dictionary entries, e.g. can be used to change the patch
    type in the field and polyMesh/boundary files.

    Reads dictionaries (fields) and entries to change from a dictionary.
38
    E.g. to make the \em movingWall a \em fixedValue for \em p but all other
39 40 41
    \em Walls a zeroGradient boundary condition, the
    \c system/changeDictionaryDict would contain the following:
    \verbatim
42
    p                           // field to change
mattijs's avatar
mattijs committed
43
    {
44
        boundaryField
mattijs's avatar
mattijs committed
45
        {
46
            ".*Wall"            // entry to change
mattijs's avatar
mattijs committed
47
            {
48 49 50 51 52 53
                type            zeroGradient;
            }
            movingWall          // entry to change
            {
                type            fixedValue;
                value           uniform 123.45;
mattijs's avatar
mattijs committed
54 55 56
            }
        }
    }
57
    \endverbatim
58
    Replacement entries starting with '~' will remove the entry.
mattijs's avatar
mattijs committed
59

mattijs's avatar
mattijs committed
60
Usage
61
    \b changeDictionary [OPTION]
mattijs's avatar
mattijs committed
62

63 64 65
    Options:
      - \par -subDict
        Specify the subDict name of the replacements dictionary.
mattijs's avatar
mattijs committed
66

67 68 69
      - \par -literalRE
        Do not interpret regular expressions or patchGroups; treat them as any
        other keyword.
mattijs's avatar
mattijs committed
70

71 72
      - \par -enableFunctionEntries
        Enable function entries (default: disabled)
73

74 75
      - \par -disablePatchGroups
        Disable the default checking for keys being patchGroups
76

mattijs's avatar
mattijs committed
77 78 79 80
\*---------------------------------------------------------------------------*/

#include "argList.H"
#include "IOobjectList.H"
81
#include "IOPtrList.H"
mattijs's avatar
mattijs committed
82
#include "volFields.H"
mattijs's avatar
mattijs committed
83
#include "stringListOps.H"
84
#include "timeSelector.H"
mattijs's avatar
mattijs committed
85 86 87 88 89

using namespace Foam;

// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

90 91 92 93 94 95
namespace Foam
{
    defineTemplateTypeNameAndDebug(IOPtrList<entry>, 0);
}


henry's avatar
henry committed
96
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
mattijs's avatar
mattijs committed
97

98
// Extract groupPatch info from boundary file info
99
HashTable<wordList> extractPatchGroups(const dictionary& boundaryDict)
100
{
101
    HashTable<wordList> groupToPatch;
102

103
    for (const entry& dEntry : boundaryDict)
104
    {
105 106 107 108 109
        if (!dEntry.isDict())
        {
            continue;
        }

110 111
        const word& patchName = dEntry.keyword();
        const dictionary& patchDict = dEntry.dict();
112

113 114 115 116
        wordList groupNames;
        patchDict.readIfPresent("inGroups", groupNames);

        for (const word& groupName : groupNames)
117
        {
118 119 120 121 122 123
            auto groupIter = groupToPatch.find(groupName);
            if (groupIter.found())
            {
                (*groupIter).append(patchName);
            }
            else
124
            {
125
                groupToPatch.insert(groupName, wordList(one(), patchName));
126 127 128
            }
        }
    }
129

130 131 132 133 134 135
    return groupToPatch;
}


bool merge
(
136
    const bool addNonExisting,
137 138 139
    dictionary&,
    const dictionary&,
    const bool,
140
    const HashTable<wordList>&
141
);
mattijs's avatar
mattijs committed
142 143 144 145 146 147 148 149


// Add thisEntry to dictionary thisDict.
bool addEntry
(
    dictionary& thisDict,
    entry& thisEntry,
    const entry& mergeEntry,
150
    const bool literalRE,
151
    const HashTable<wordList>& shortcuts
mattijs's avatar
mattijs committed
152 153 154 155 156 157 158 159 160 161 162 163
)
{
    bool changed = false;

    // Recursively merge sub-dictionaries
    // TODO: merge without copying
    if (thisEntry.isDict() && mergeEntry.isDict())
    {
        if
        (
            merge
            (
164
                true,
mattijs's avatar
mattijs committed
165 166
                const_cast<dictionary&>(thisEntry.dict()),
                mergeEntry.dict(),
167 168
                literalRE,
                shortcuts
mattijs's avatar
mattijs committed
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
            )
        )
        {
            changed = true;
        }
    }
    else
    {
        // Should use in-place modification instead of adding
        thisDict.add(mergeEntry.clone(thisDict).ptr(), true);
        changed = true;
    }

    return changed;
}


186 187 188
// List of indices into thisKeys
labelList findMatches
(
189
    const HashTable<wordList>& shortcuts,
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
    const wordList& shortcutNames,
    const wordList& thisKeys,
    const keyType& key
)
{
    labelList matches;

    if (key.isPattern())
    {
        // Wildcard match
        matches = findStrings(key, thisKeys);
    }
    else if (shortcuts.size())
    {
        // See if patchGroups expand to valid thisKeys
        labelList indices = findStrings(key, shortcutNames);
206 207

        for (const label idx : indices)
208
        {
209
            const word& name = shortcutNames[idx];
210 211 212
            const wordList& keys = shortcuts[name];
            forAll(keys, j)
            {
213
                const label index = thisKeys.find(keys[j]);
214 215 216 217 218 219 220 221 222 223 224
                if (index != -1)
                {
                    matches.append(index);
                }
            }
        }
    }
    return matches;
}


mattijs's avatar
mattijs committed
225
// Dictionary merging/editing.
mattijs's avatar
mattijs committed
226 227
// literalRE:
// - true: behave like dictionary::merge, i.e. add regexps just like
mattijs's avatar
mattijs committed
228 229 230 231
//   any other key.
// - false : interpret wildcard as a rule for items to be matched.
bool merge
(
232
    const bool addNonExisting,
mattijs's avatar
mattijs committed
233 234
    dictionary& thisDict,
    const dictionary& mergeDict,
235
    const bool literalRE,
236
    const HashTable<wordList>& shortcuts
mattijs's avatar
mattijs committed
237 238
)
{
239 240
    const wordList shortcutNames(shortcuts.toc());

mattijs's avatar
mattijs committed
241 242 243
    bool changed = false;

    // Save current (non-wildcard) keys before adding items.
244
    wordHashSet thisKeysSet;
mattijs's avatar
mattijs committed
245
    {
246
        for (const word& k : thisDict.keys(false))
mattijs's avatar
mattijs committed
247
        {
248
            thisKeysSet.insert(k);
mattijs's avatar
mattijs committed
249 250 251 252 253
        }
    }

    // Pass 1. All literal matches

254
    for (const entry& mergeEntry : mergeDict)
mattijs's avatar
mattijs committed
255
    {
256
        const keyType& key = mergeEntry.keyword();
mattijs's avatar
mattijs committed
257

258 259
        if (key[0] == '~')
        {
260
            const word eraseKey = key.substr(1);
261 262 263 264 265 266 267 268 269
            if (thisDict.remove(eraseKey))
            {
                // Mark thisDict entry as having been match for wildcard
                // handling later on.
                thisKeysSet.erase(eraseKey);
            }
            changed = true;
        }
        else if (literalRE || !(key.isPattern() || shortcuts.found(key)))
mattijs's avatar
mattijs committed
270
        {
271
            entry* eptr = thisDict.findEntry(key, keyType::LITERAL);
mattijs's avatar
mattijs committed
272

273
            if (eptr)
mattijs's avatar
mattijs committed
274 275 276
            {
                // Mark thisDict entry as having been match for wildcard
                // handling later on.
277
                thisKeysSet.erase(eptr->keyword());
mattijs's avatar
mattijs committed
278 279 280 281 282 283

                if
                (
                    addEntry
                    (
                        thisDict,
284
                       *eptr,
285
                        mergeEntry,
286 287
                        literalRE,
                        shortcuts
mattijs's avatar
mattijs committed
288 289 290 291 292 293 294 295
                    )
                )
                {
                    changed = true;
                }
            }
            else
            {
296 297
                if (addNonExisting)
                {
298 299
                    // Not found - just add
                    thisDict.add(mergeEntry.clone(thisDict).ptr());
300 301 302 303 304 305 306 307
                    changed = true;
                }
                else
                {
                    IOWarningInFunction(mergeDict)
                        << "Ignoring non-existing entry " << key
                        << endl;
                }
mattijs's avatar
mattijs committed
308 309 310 311 312
            }
        }
    }


313
    // Pass 2. Wildcard or shortcut matches (if any) on any non-match keys.
mattijs's avatar
mattijs committed
314

315
    if (!literalRE && thisKeysSet.size())
mattijs's avatar
mattijs committed
316
    {
317
        // Pick up remaining dictionary entries
mattijs's avatar
mattijs committed
318 319
        wordList thisKeys(thisKeysSet.toc());

320
        for (const entry& mergeEntry : mergeDict)
mattijs's avatar
mattijs committed
321
        {
322
            const keyType& key = mergeEntry.keyword();
mattijs's avatar
mattijs committed
323

324
            if (key[0] == '~')
mattijs's avatar
mattijs committed
325
            {
326
                const word eraseKey = key.substr(1);
mattijs's avatar
mattijs committed
327

328 329 330 331 332 333 334 335 336 337 338 339 340
                // List of indices into thisKeys
                labelList matches
                (
                    findMatches
                    (
                        shortcuts,
                        shortcutNames,
                        thisKeys,
                        eraseKey
                    )
                );

                // Remove all matches
341
                for (const label matchi : matches)
mattijs's avatar
mattijs committed
342
                {
343 344
                    const word& k = thisKeys[matchi];
                    thisKeysSet.erase(k);
345
                }
346
                changed = true;
347
            }
348
            else
349
            {
350 351
                // List of indices into thisKeys
                labelList matches
352
                (
353 354 355 356 357 358 359
                    findMatches
                    (
                        shortcuts,
                        shortcutNames,
                        thisKeys,
                        key
                    )
360
                );
mattijs's avatar
mattijs committed
361

362
                // Add all matches
363
                for (const label matchi : matches)
364
                {
365
                    const word& k = thisKeys[matchi];
366

367
                    entry* eptr = thisDict.findEntry(k, keyType::LITERAL);
368 369 370 371 372 373

                    if
                    (
                        addEntry
                        (
                            thisDict,
374
                           *eptr,
375
                            mergeEntry,
376
                            literalRE,
377 378
                            HashTable<wordList>(0)    // no shortcuts
                                                      // at deeper levels
379
                        )
mattijs's avatar
mattijs committed
380
                    )
381 382 383
                    {
                        changed = true;
                    }
mattijs's avatar
mattijs committed
384 385 386 387 388 389 390 391 392
                }
            }
        }
    }

    return changed;
}


mattijs's avatar
mattijs committed
393 394 395

int main(int argc, char *argv[])
{
396 397 398 399 400 401
    argList::addNote
    (
        "Utility to change dictionary entries"
        " (such as the patch type for fields and polyMesh/boundary files)."
    );

402
    argList::addOption("dict", "file", "Alternative changeDictionaryDict");
403

404
    argList::addOption
405 406 407
    (
        "subDict",
        "name",
408
        "Specify the subDict name of the replacements dictionary"
409 410
    );
    argList::addOption
411 412 413
    (
        "instance",
        "name",
414
        "Override instance setting (default is the time name)"
415
    );
416 417 418 419

    // Add explicit time option
    timeSelector::addOptions();

420 421 422
    argList::addBoolOption
    (
        "literalRE",
423
        "Treat regular expressions literally (i.e., as a keyword)"
424
    );
425 426 427
    argList::addBoolOption
    (
        "enableFunctionEntries",
428
        "Enable expansion of dictionary directives - #include, #codeStream etc"
429
    );
430 431 432
    argList::addBoolOption
    (
        "disablePatchGroups",
433
        "Disable matching keys to patch groups"
434 435
    );

436 437 438 439
    #include "addRegionOption.H"

    #include "setRootCase.H"
    #include "createTime.H"
440 441 442 443 444

    // Optionally override controlDict time with -time options
    instantList times = timeSelector::selectIfPresent(runTime, args);
    if (times.size() < 1)
    {
445
        FatalErrorInFunction
446 447
            << "No times selected." << exit(FatalError);
    }
448 449 450
    forAll(times, timei)
    {
        word instance;
451
        if (args.readIfPresent("instance", instance))
452 453 454 455 456 457 458 459 460 461 462 463 464
        {
            if (times.size() > 1)
            {
                FatalErrorInFunction
                    << "Multiple times selected with 'instance' option"
                    << exit(FatalError);
            }
        }
        else
        {
            runTime.setTime(times[timei], timei);
            instance = runTime.timeName();
        }
mattijs's avatar
mattijs committed
465

466
        #include "createNamedMesh.H"
mattijs's avatar
mattijs committed
467

468
        const bool literalRE = args.found("literalRE");
469 470 471 472 473 474 475
        if (literalRE)
        {
            Info<< "Not interpreting any regular expressions (RE)"
                << " in the changeDictionaryDict." << endl
                << "Instead they are handled as any other entry, i.e. added if"
                << " not present." << endl;
        }
476

477
        const bool enableEntries = args.found("enableFunctionEntries");
478 479
        if (enableEntries)
        {
480
            Info<< "Allowing dictionary preprocessing (#include, #codeStream)."
481 482
                << endl;
        }
483

484 485 486 487 488 489
        const int oldFlag = entry::disableFunctionEntries;
        if (!enableEntries)
        {
            // By default disable dictionary expansion for fields
            entry::disableFunctionEntries = 1;
        }
mattijs's avatar
mattijs committed
490

491

492
        const bool disablePatchGroups = args.found("disablePatchGroups");
493 494 495 496 497 498
        if (disablePatchGroups)
        {
            Info<< "Not interpreting any keys in the changeDictionary"
                << " as patchGroups"
                << endl;
        }
499

Mattijs Janssens's avatar
Mattijs Janssens committed
500

501
        fileName regionPrefix;
502 503 504 505
        if (regionName != fvMesh::defaultRegion)
        {
            regionPrefix = regionName;
        }
506

507

508 509 510
        // Make sure we do not use the master-only reading since we read
        // fields (different per processor) as dictionaries.
        regIOobject::fileModificationChecking = regIOobject::timeStamp;
511

512

513
        // Get the replacement rules from a dictionary
Henry's avatar
Henry committed
514

515 516 517
        const word dictName("changeDictionaryDict");
        #include "setSystemMeshDictionaryIO.H"
        IOdictionary dict(dictIO);
518

519
        const dictionary* replaceDictsPtr = &dict;
520

521
        if (args.found("subDict"))
522
        {
523
            replaceDictsPtr = &dict.subDict(args["subDict"]);
524
        }
525

526
        const dictionary& replaceDicts = *replaceDictsPtr;
mattijs's avatar
mattijs committed
527

528 529 530
        Info<< "Read dictionary " << dict.name()
            << " with replacements for dictionaries "
            << replaceDicts.toc() << endl;
mattijs's avatar
mattijs committed
531

532 533


534 535
        // Always read boundary to get patch groups
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
536

537 538 539 540 541 542 543
        Info<< "Reading polyMesh/boundary file to extract patch names"
            << endl;

        // Read PtrList of dictionary as dictionary.
        const word oldTypeName = IOPtrList<entry>::typeName;
        const_cast<word&>(IOPtrList<entry>::typeName) = word::null;
        IOPtrList<entry> dictList
544
        (
545
            IOobject
546 547
            (
                "boundary",
548 549 550 551 552 553 554 555 556 557 558 559 560 561
                runTime.findInstance
                (
                    regionPrefix/polyMesh::meshSubDir,
                    "boundary",
                    IOobject::READ_IF_PRESENT
                ),
                polyMesh::meshSubDir,
                mesh,
                IOobject::READ_IF_PRESENT,
                IOobject::NO_WRITE,
                false
            )
        );
        const_cast<word&>(IOPtrList<entry>::typeName) = oldTypeName;
562

563 564
        // Fake type back to what was in field
        const_cast<word&>(dictList.type()) = dictList.headerClassName();
565

566 567
        // Temporary convert to dictionary
        dictionary fieldDict;
568
        for (const entry& e : dictList)
569
        {
570 571 572 573
            if (e.isDict())
            {
                fieldDict.add(e.keyword(), e.dict());
            }
574
        }
575

576 577 578 579 580
        if (dictList.size())
        {
            Info<< "Loaded dictionary " << dictList.name()
                << " with entries " << fieldDict.toc() << endl;
        }
581

582 583
        // Extract any patchGroups information (= shortcut for set of
        // patches)
584
        HashTable<wordList> patchGroups;
585
        if (!disablePatchGroups)
586
        {
587 588
            patchGroups = extractPatchGroups(fieldDict);
            if (patchGroups.size())
589
            {
590 591 592 593 594 595 596
                Info<< "Extracted patch groups:" << endl;
                wordList groups(patchGroups.sortedToc());
                forAll(groups, i)
                {
                    Info<< "    group " << groups[i] << " with patches "
                        << patchGroups[groups[i]] << endl;
                }
597 598 599 600
            }
        }


601
        // Every replacement is a dictionary name and a keyword in this
mattijs's avatar
mattijs committed
602

603
        for (const entry& replaceEntry : replaceDicts)
604
        {
605 606 607 608 609 610
            if (!replaceEntry.isDict())
            {
                // Could also warn
                continue;
            }

611 612 613
            const word& fieldName = replaceEntry.keyword();
            const dictionary& replaceDict = replaceEntry.dict();

614
            Info<< "Replacing entries in dictionary " << fieldName << endl;
615

616 617 618 619 620 621 622
            // Handle 'boundary' specially:
            // - is PtrList of dictionaries
            // - is in polyMesh/
            if (fieldName == "boundary")
            {
                Info<< "Special handling of " << fieldName
                    << " as polyMesh/boundary file." << endl;
623

624
                // Merge the replacements in. Do not add non-existing entries.
625
                Info<< "Merging entries from " << replaceDict.toc() << endl;
626
                merge(false, fieldDict, replaceDict, literalRE, patchGroups);
627

628
                Info<< "fieldDict:" << fieldDict << endl;
629

630 631
                // Convert back into dictList
                wordList doneKeys(dictList.size());
632

633
                label nEntries = fieldDict.size();
634
                nEntries = 0;
635 636 637 638

                forAll(dictList, i)
                {
                    doneKeys[i] = dictList[i].keyword();
639 640

                    const entry* ePtr = fieldDict.findEntry
mattijs's avatar
mattijs committed
641
                    (
642 643
                        doneKeys[i],
                        keyType::REGEX
644
                    );
645 646 647 648 649 650
                    // Check that it hasn't been removed from fieldDict
                    if (ePtr)
                    {
                        dictList.set(nEntries++, ePtr->clone());
                        fieldDict.remove(doneKeys[i]);
                    }
651 652 653
                }

                // Add remaining entries
654
                for (const entry& e : fieldDict)
655
                {
656
                    dictList.set(nEntries++, e.clone());
657
                }
658
                dictList.setSize(nEntries);
659 660 661 662

                Info<< "Writing modified " << fieldName << endl;
                dictList.writeObject
                (
663
                    IOstreamOption(runTime.writeFormat()),
664
                    true
mattijs's avatar
mattijs committed
665
                );
666
            }
667
            else
668
            {
669 670 671 672 673
                // Read dictionary
                // Note: disable class type checking so we can load field
                Info<< "Loading dictionary " << fieldName << endl;
                const word oldTypeName = IOdictionary::typeName;
                const_cast<word&>(IOdictionary::typeName) = word::null;
674

675 676 677 678 679 680 681 682 683
                IOobject fieldHeader
                (
                    fieldName,
                    instance,
                    mesh,
                    IOobject::MUST_READ_IF_MODIFIED,
                    IOobject::NO_WRITE,
                    false
                );
684

685 686 687
                if (fieldHeader.typeHeaderOk<IOdictionary>(false))
                {
                    IOdictionary fieldDict(fieldHeader);
688

689
                    const_cast<word&>(IOdictionary::typeName) = oldTypeName;
690

691 692 693
                    // Fake type back to what was in field
                    const_cast<word&>(fieldDict.type()) =
                        fieldDict.headerClassName();
694

695 696
                    Info<< "Loaded dictionary " << fieldName
                        << " with entries " << fieldDict.toc() << endl;
697

698
                    // Merge the replacements in (allow adding)
699
                    Info<< "Merging entries from " << replaceDict.toc() << endl;
700
                    merge(true, fieldDict, replaceDict, literalRE, patchGroups);
701

702 703 704 705 706 707 708 709 710
                    Info<< "Writing modified fieldDict " << fieldName << endl;
                    fieldDict.regIOobject::write();
                }
                else
                {
                    WarningInFunction
                        << "Requested field to change " << fieldName
                        << " does not exist in " << fieldHeader.path() << endl;
                }
711
            }
712 713

            entry::disableFunctionEntries = oldFlag;
714
        }
mattijs's avatar
mattijs committed
715 716
    }

717
    Info<< "\nEnd\n" << endl;
mattijs's avatar
mattijs committed
718 719 720 721

    return 0;
}

henry's avatar
henry committed
722

mattijs's avatar
mattijs committed
723
// ************************************************************************* //