diff --git a/docs/theory/unsrtnat.bst b/docs/theory/apalike.bst similarity index 60% copy from docs/theory/unsrtnat.bst copy to docs/theory/apalike.bst index 1c7eddf..65d2585 100644 --- a/docs/theory/unsrtnat.bst +++ b/docs/theory/apalike.bst @@ -1,1343 +1,1105 @@ -%% File: `unsrtnat.bst' -%% A modification of `unsrt.bst' for use with natbib package -%% -%% Copyright 1993-2007 Patrick W Daly -%% Max-Planck-Institut f\"ur Sonnensystemforschung -%% Max-Planck-Str. 2 -%% D-37191 Katlenburg-Lindau -%% Germany -%% E-mail: daly@mps.mpg.de -%% -%% This program can be redistributed and/or modified under the terms -%% of the LaTeX Project Public License Distributed from CTAN -%% archives in directory macros/latex/base/lppl.txt; either -%% version 1 of the License, or any later version. -%% - % Version and source file information: - % \ProvidesFile{natbst.mbs}[2007/11/26 1.93 (PWD)] - % - % BibTeX `plainnat' family - % version 0.99b for BibTeX versions 0.99a or later, - % for LaTeX versions 2.09 and 2e. - % - % For use with the `natbib.sty' package; emulates the corresponding - % member of the `plain' family, but with author-year citations. - % - % With version 6.0 of `natbib.sty', it may also be used for numerical - % citations, while retaining the commands \citeauthor, \citefullauthor, - % and \citeyear to print the corresponding information. - % - % For version 7.0 of `natbib.sty', the KEY field replaces missing - % authors/editors, and the date is left blank in \bibitem. - % - % Includes field EID for the sequence/citation number of electronic journals - % which is used instead of page numbers. - % - % Includes fields ISBN and ISSN. - % - % Includes field URL for Internet addresses. - % - % Includes field DOI for Digital Object Idenfifiers. - % - % Works best with the url.sty package of Donald Arseneau. - % - % Works with identical authors and year are further sorted by - % title text, as in the standard plain.bst etc. - % +% BibTeX `apalike' bibliography style (version 0.99a, 8-Dec-10), adapted from +% the `alpha' style, version 0.99a; for BibTeX version 0.99a. +% +% Copyright (C) 1988, 2010 Oren Patashnik. +% Unlimited copying and redistribution of this file are permitted as long as +% it is unmodified. Modifications (and redistribution of modified versions) +% are also permitted, but only if the resulting file is renamed. +% +% Differences between this style and `alpha' are generally heralded by a `%'. +% The file btxbst.doc has the documentation for alpha.bst. +% +% This style should be used with the `apalike' LaTeX style (apalike.sty). +% \cite's come out like "(Jones, 1986)" in the text but there are no labels +% in the bibliography, and something like "(1986)" comes out immediately +% after the author. Author (and editor) names appear as last name, comma, +% initials. A `year' field is required for every entry, and so is either +% an author (or in some cases, an editor) field or a key field. +% +% Editorial note: +% Many journals require a style like `apalike', but I strongly, strongly, +% strongly recommend that you not use it if you have a choice---use something +% like `plain' instead. Mary-Claire van Leunen (A Handbook for Scholars, +% Knopf, 1979) argues convincingly that a style like `plain' encourages better +% writing than one like `apalike'. Furthermore the strongest arguments for +% using an author-date style like `apalike'---that it's "the most practical" +% (The Chicago Manual of Style, University of Chicago Press, thirteenth +% edition, 1982, pages 400--401)---fall flat on their face with the new +% computer-typesetting technology. For instance page 401 anachronistically +% states "The chief disadvantage of [a style like `plain'] is that additions +% or deletions cannot be made after the manuscript is typed without changing +% numbers in both text references and list." LaTeX sidesteps the disadvantage. +% +% History: +% 15-sep-86 (OP) Original version by Oren Patashnik, ideas from Susan King. +% 10-nov-86 (OP) Truncated the sort.key$ string to the correct length +% in bib.sort.order to eliminate error message. +% 24-jan-88 (OP) Updated for BibTeX version 0.99a, from alpha.bst 0.99a; +% apalike now sorts by author, then year, then title; +% THIS `apalike' VERSION DOES NOT WORK WITH BIBTEX 0.98i. +% 8-dec-10 (OP) Still version 0.99a, as the code itself was unchanged; +% this release clarified the license. + ENTRY { address author booktitle chapter - doi - eprint - eid edition editor howpublished institution - isbn - issn journal key - month +% month not used in apalike note number organization pages publisher school series title type - url volume year } {} - { label extra.label sort.label short.list } + { label extra.label sort.label } INTEGERS { output.state before.all mid.sentence after.sentence after.block } FUNCTION {init.state.consts} { #0 'before.all := #1 'mid.sentence := #2 'after.sentence := #3 'after.block := } STRINGS { s t } FUNCTION {output.nonnull} { 's := output.state mid.sentence = { ", " * write$ } { output.state after.block = { add.period$ write$ newline$ "\newblock " write$ } { output.state before.all = 'write$ { add.period$ " " * write$ } if$ } if$ mid.sentence 'output.state := } if$ s } FUNCTION {output} { duplicate$ empty$ 'pop$ 'output.nonnull if$ } FUNCTION {output.check} { 't := duplicate$ empty$ { pop$ "empty " t * " in " * cite$ * warning$ } 'output.nonnull if$ } +% apalike needs this function because +% the year has special punctuation; +% apalike ignores the month +FUNCTION {output.year.check} +{ year empty$ + { "empty year in " cite$ * warning$ } + { write$ + " (" year * extra.label * ")" * + mid.sentence 'output.state := + } + if$ +} + +FUNCTION {output.bibitem} +{ newline$ + "\bibitem[" write$ + label write$ + "]{" write$ + cite$ write$ + "}" write$ + newline$ + "" + before.all 'output.state := +} + FUNCTION {fin.entry} { add.period$ write$ newline$ } FUNCTION {new.block} { output.state before.all = 'skip$ { after.block 'output.state := } if$ } FUNCTION {new.sentence} { output.state after.block = 'skip$ { output.state before.all = 'skip$ { after.sentence 'output.state := } if$ } if$ } FUNCTION {not} { { #0 } { #1 } if$ } FUNCTION {and} { 'skip$ { pop$ #0 } if$ } FUNCTION {or} { { pop$ #1 } 'skip$ if$ } -FUNCTION {new.block.checka} -{ empty$ - 'skip$ - 'new.block - if$ -} - FUNCTION {new.block.checkb} { empty$ swap$ empty$ and 'skip$ 'new.block if$ } -FUNCTION {new.sentence.checka} -{ empty$ - 'skip$ - 'new.sentence - if$ -} - -FUNCTION {new.sentence.checkb} -{ empty$ - swap$ empty$ - and - 'skip$ - 'new.sentence - if$ -} - FUNCTION {field.or.null} { duplicate$ empty$ { pop$ "" } 'skip$ if$ } FUNCTION {emphasize} { duplicate$ empty$ { pop$ "" } - { "\emph{" swap$ * "}" * } + { "{\em " swap$ * "}" * } if$ } INTEGERS { nameptr namesleft numnames } FUNCTION {format.names} { 's := #1 'nameptr := s num.names$ 'numnames := numnames 'namesleft := { namesleft #0 > } - { s nameptr "{ff~}{vv~}{ll}{, jj}" format.name$ 't := + { s nameptr "{vv~}{ll}{, jj}{, ff~}" format.name$ 't := % last name first nameptr #1 > { namesleft #1 > { ", " * t * } { numnames #2 > { "," * } 'skip$ if$ t "others" = { " et~al." * } { " and " * t * } if$ } if$ } 't if$ nameptr #1 + 'nameptr := namesleft #1 - 'namesleft := } while$ } -FUNCTION {format.key} -{ empty$ - { key field.or.null } +FUNCTION {format.authors} +{ author empty$ { "" } + { author format.names } if$ } -FUNCTION {format.authors} -{ author empty$ +FUNCTION {format.key} % this function is just for apalike +{ empty$ + { key field.or.null } { "" } - { author format.names } if$ } FUNCTION {format.editors} { editor empty$ { "" } { editor format.names editor num.names$ #1 > { ", editors" * } { ", editor" * } if$ } if$ } -FUNCTION {format.isbn} -{ isbn empty$ - { "" } - { new.block "ISBN " isbn * } - if$ -} - -FUNCTION {format.issn} -{ issn empty$ - { "" } - { new.block "ISSN " issn * } - if$ -} - -FUNCTION {format.url} -{ url empty$ - { "" } - { new.block "URL \url{" url * "}" * } - if$ -} - -FUNCTION {format.doi} -{ doi empty$ - { "" } - { new.block "\doi{" doi * "}" * } - if$ -} - -FUNCTION {format.eprint} -{ eprint empty$ - { "" } - { new.block "\eprint{" eprint * "}" * } - if$ -} - FUNCTION {format.title} { title empty$ { "" } { title "t" change.case$ } if$ } -FUNCTION {format.full.names} -{'s := - #1 'nameptr := - s num.names$ 'numnames := - numnames 'namesleft := - { namesleft #0 > } - { s nameptr - "{vv~}{ll}" format.name$ 't := - nameptr #1 > - { - namesleft #1 > - { ", " * t * } - { - numnames #2 > - { "," * } - 'skip$ - if$ - t "others" = - { " et~al." * } - { " and " * t * } - if$ - } - if$ - } - 't - if$ - nameptr #1 + 'nameptr := - namesleft #1 - 'namesleft := - } - while$ -} - -FUNCTION {author.editor.full} -{ author empty$ - { editor empty$ - { "" } - { editor format.full.names } - if$ - } - { author format.full.names } - if$ -} - -FUNCTION {author.full} -{ author empty$ - { "" } - { author format.full.names } - if$ -} - -FUNCTION {editor.full} -{ editor empty$ - { "" } - { editor format.full.names } - if$ -} - -FUNCTION {make.full.names} -{ type$ "book" = - type$ "inbook" = - or - 'author.editor.full - { type$ "proceedings" = - 'editor.full - 'author.full - if$ - } - if$ -} - -FUNCTION {output.bibitem} -{ newline$ - "\bibitem[" write$ - label write$ - ")" make.full.names duplicate$ short.list = - { pop$ } - { * } - if$ - "]{" * write$ - cite$ write$ - "}" write$ - newline$ - "" - before.all 'output.state := -} - FUNCTION {n.dashify} { 't := "" { t empty$ not } { t #1 #1 substring$ "-" = { t #1 #2 substring$ "--" = not { "--" * t #2 global.max$ substring$ 't := } { { t #1 #1 substring$ "-" = } { "-" * t #2 global.max$ substring$ 't := } while$ } if$ } { t #1 #1 substring$ * t #2 global.max$ substring$ 't := } if$ } while$ } -FUNCTION {format.date} -{ year duplicate$ empty$ - { "empty year in " cite$ * warning$ - pop$ "" } - 'skip$ - if$ - month empty$ - 'skip$ - { month - " " * swap$ * - } - if$ - extra.label * -} - FUNCTION {format.btitle} { title emphasize } FUNCTION {tie.or.space.connect} { duplicate$ text.length$ #3 < { "~" } { " " } if$ swap$ * * } FUNCTION {either.or.check} { empty$ 'pop$ { "can't use both " swap$ * " fields in " * cite$ * warning$ } if$ } FUNCTION {format.bvolume} { volume empty$ { "" } { "volume" volume tie.or.space.connect series empty$ 'skip$ { " of " * series emphasize * } if$ "volume and number" number either.or.check } if$ } FUNCTION {format.number.series} { volume empty$ { number empty$ { series field.or.null } { output.state mid.sentence = { "number" } { "Number" } if$ number tie.or.space.connect series empty$ { "there's a number but no series in " cite$ * warning$ } { " in " * series * } if$ } if$ } { "" } if$ } FUNCTION {format.edition} { edition empty$ { "" } { output.state mid.sentence = { edition "l" change.case$ " edition" * } { edition "t" change.case$ " edition" * } if$ } if$ } INTEGERS { multiresult } FUNCTION {multi.page.check} { 't := #0 'multiresult := { multiresult not t empty$ not and } { t #1 #1 substring$ duplicate$ "-" = swap$ duplicate$ "," = swap$ "+" = or or { #1 'multiresult := } { t #2 global.max$ substring$ 't := } if$ } while$ multiresult } FUNCTION {format.pages} { pages empty$ { "" } { pages multi.page.check { "pages" pages n.dashify tie.or.space.connect } { "page" pages tie.or.space.connect } if$ } if$ } -FUNCTION {format.eid} -{ eid empty$ - { "" } - { "art." eid tie.or.space.connect } - if$ -} - FUNCTION {format.vol.num.pages} { volume field.or.null number empty$ 'skip$ - { "\penalty0 (" number * ")" * * + { "(" number * ")" * * volume empty$ { "there's a number but no volume in " cite$ * warning$ } 'skip$ if$ } if$ pages empty$ 'skip$ { duplicate$ empty$ { pop$ format.pages } - { ":\penalty0 " * pages n.dashify * } - if$ - } - if$ -} - -FUNCTION {format.vol.num.eid} -{ volume field.or.null - number empty$ - 'skip$ - { "\penalty0 (" number * ")" * * - volume empty$ - { "there's a number but no volume in " cite$ * warning$ } - 'skip$ - if$ - } - if$ - eid empty$ - 'skip$ - { duplicate$ empty$ - { pop$ format.eid } - { ":\penalty0 " * eid * } + { ":" * pages n.dashify * } if$ } if$ } FUNCTION {format.chapter.pages} { chapter empty$ 'format.pages { type empty$ { "chapter" } { type "l" change.case$ } if$ chapter tie.or.space.connect pages empty$ 'skip$ { ", " * format.pages * } if$ } if$ } FUNCTION {format.in.ed.booktitle} { booktitle empty$ { "" } { editor empty$ { "In " booktitle emphasize * } { "In " format.editors * ", " * booktitle emphasize * } if$ } if$ } -FUNCTION {empty.misc.check} -{ author empty$ title empty$ howpublished empty$ - month empty$ year empty$ note empty$ - and and and and and - key empty$ not and - { "all relevant fields are empty in " cite$ * warning$ } - 'skip$ - if$ -} - FUNCTION {format.thesis.type} { type empty$ 'skip$ { pop$ type "t" change.case$ } if$ } FUNCTION {format.tr.number} { type empty$ { "Technical Report" } 'type if$ number empty$ { "t" change.case$ } { number tie.or.space.connect } if$ } FUNCTION {format.article.crossref} -{ key empty$ - { journal empty$ - { "need key or journal for " cite$ * " to crossref " * crossref * - warning$ - "" - } - { "In \emph{" journal * "}" * } - if$ - } - { "In " } - if$ - " \citet{" * crossref * "}" * +{ "In" % this is for apalike + " \cite{" * crossref * "}" * } FUNCTION {format.book.crossref} { volume empty$ { "empty volume in " cite$ * "'s crossref of " * crossref * warning$ "In " } { "Volume" volume tie.or.space.connect " of " * } if$ - editor empty$ - editor field.or.null author field.or.null = - or - { key empty$ - { series empty$ - { "need editor, key, or series for " cite$ * " to crossref " * - crossref * warning$ - "" * - } - { "\emph{" * series * "}" * } - if$ - } - 'skip$ - if$ - } - 'skip$ - if$ - " \citet{" * crossref * "}" * + "\cite{" * crossref * "}" * % this is for apalike } FUNCTION {format.incoll.inproc.crossref} -{ editor empty$ - editor field.or.null author field.or.null = - or - { key empty$ - { booktitle empty$ - { "need editor, key, or booktitle for " cite$ * " to crossref " * - crossref * warning$ - "" - } - { "In \emph{" booktitle * "}" * } - if$ - } - { "In " } - if$ - } - { "In " } - if$ - " \citet{" * crossref * "}" * +{ "In" % this is for apalike + " \cite{" * crossref * "}" * } FUNCTION {article} { output.bibitem format.authors "author" output.check - author format.key output + author format.key output % special for + output.year.check % apalike new.block format.title "title" output.check new.block crossref missing$ { journal emphasize "journal" output.check - eid empty$ - { format.vol.num.pages output } - { format.vol.num.eid output } - if$ - format.date "year" output.check + format.vol.num.pages output } { format.article.crossref output.nonnull - eid empty$ - { format.pages output } - { format.eid output } - if$ + format.pages output } if$ - format.issn output - format.doi output - format.eprint output - format.url output new.block note output fin.entry } FUNCTION {book} { output.bibitem author empty$ { format.editors "author and editor" output.check editor format.key output } { format.authors output.nonnull crossref missing$ { "author and editor" editor either.or.check } 'skip$ if$ } if$ + output.year.check % special for apalike new.block format.btitle "title" output.check crossref missing$ { format.bvolume output new.block format.number.series output new.sentence publisher "publisher" output.check address output } { new.block format.book.crossref output.nonnull } if$ format.edition output - format.date "year" output.check - format.isbn output - format.doi output - format.eprint output - format.url output new.block note output fin.entry } FUNCTION {booklet} { output.bibitem format.authors output - author format.key output + author format.key output % special for + output.year.check % apalike new.block format.title "title" output.check - howpublished address new.block.checkb + new.block howpublished output address output - format.date output - format.isbn output - format.doi output - format.eprint output - format.url output new.block note output fin.entry } FUNCTION {inbook} { output.bibitem author empty$ { format.editors "author and editor" output.check editor format.key output } { format.authors output.nonnull crossref missing$ { "author and editor" editor either.or.check } 'skip$ if$ } if$ + output.year.check % special for apalike new.block format.btitle "title" output.check crossref missing$ { format.bvolume output format.chapter.pages "chapter and pages" output.check new.block format.number.series output new.sentence publisher "publisher" output.check address output } { format.chapter.pages "chapter and pages" output.check new.block format.book.crossref output.nonnull } if$ format.edition output - format.date "year" output.check - format.isbn output - format.doi output - format.eprint output - format.url output new.block note output fin.entry } FUNCTION {incollection} { output.bibitem format.authors "author" output.check - author format.key output + author format.key output % special for + output.year.check % apalike new.block format.title "title" output.check new.block crossref missing$ { format.in.ed.booktitle "booktitle" output.check format.bvolume output format.number.series output format.chapter.pages output new.sentence publisher "publisher" output.check address output format.edition output - format.date "year" output.check } { format.incoll.inproc.crossref output.nonnull format.chapter.pages output } if$ - format.isbn output - format.doi output - format.eprint output - format.url output new.block note output fin.entry } FUNCTION {inproceedings} { output.bibitem format.authors "author" output.check - author format.key output + author format.key output % special for + output.year.check % apalike new.block format.title "title" output.check new.block crossref missing$ { format.in.ed.booktitle "booktitle" output.check format.bvolume output format.number.series output format.pages output - address empty$ - { organization publisher new.sentence.checkb - organization output - publisher output - format.date "year" output.check - } - { address output.nonnull - format.date "year" output.check - new.sentence - organization output - publisher output - } - if$ + address output % for apalike + new.sentence % there's no year + organization output % here so things + publisher output % are simpler } { format.incoll.inproc.crossref output.nonnull format.pages output } if$ - format.isbn output - format.doi output - format.eprint output - format.url output new.block note output fin.entry } FUNCTION {conference} { inproceedings } FUNCTION {manual} { output.bibitem format.authors output - author format.key output + author format.key output % special for + output.year.check % apalike new.block format.btitle "title" output.check organization address new.block.checkb organization output address output format.edition output - format.date output - format.url output new.block note output fin.entry } FUNCTION {mastersthesis} { output.bibitem format.authors "author" output.check - author format.key output + author format.key output % special for + output.year.check % apalike new.block format.title "title" output.check new.block "Master's thesis" format.thesis.type output.nonnull school "school" output.check address output - format.date "year" output.check - format.url output new.block note output fin.entry } FUNCTION {misc} { output.bibitem format.authors output - author format.key output - title howpublished new.block.checkb + author format.key output % special for + output.year.check % apalike + new.block format.title output - howpublished new.block.checka + new.block howpublished output - format.date output - format.issn output - format.url output new.block note output fin.entry - empty.misc.check } FUNCTION {phdthesis} { output.bibitem format.authors "author" output.check - author format.key output + author format.key output % special for + output.year.check % apalike new.block format.btitle "title" output.check new.block "PhD thesis" format.thesis.type output.nonnull school "school" output.check address output - format.date "year" output.check - format.url output new.block note output fin.entry } FUNCTION {proceedings} { output.bibitem format.editors output - editor format.key output + editor format.key output % special for + output.year.check % apalike new.block format.btitle "title" output.check format.bvolume output format.number.series output - address output - format.date "year" output.check - new.sentence - organization output - publisher output - format.isbn output - format.doi output - format.eprint output - format.url output + address output % for apalike + new.sentence % we always output + organization output % a nonempty organization + publisher output % here new.block note output fin.entry } FUNCTION {techreport} { output.bibitem format.authors "author" output.check - author format.key output + author format.key output % special for + output.year.check % apalike new.block format.title "title" output.check new.block format.tr.number output.nonnull institution "institution" output.check address output - format.date "year" output.check - format.url output new.block note output fin.entry } FUNCTION {unpublished} { output.bibitem format.authors "author" output.check - author format.key output + author format.key output % special for + output.year.check % apalike new.block format.title "title" output.check new.block note "note" output.check - format.date output - format.url output fin.entry } FUNCTION {default.type} { misc } - MACRO {jan} {"January"} MACRO {feb} {"February"} MACRO {mar} {"March"} MACRO {apr} {"April"} MACRO {may} {"May"} MACRO {jun} {"June"} MACRO {jul} {"July"} MACRO {aug} {"August"} MACRO {sep} {"September"} MACRO {oct} {"October"} MACRO {nov} {"November"} MACRO {dec} {"December"} - - MACRO {acmcs} {"ACM Computing Surveys"} MACRO {acta} {"Acta Informatica"} MACRO {cacm} {"Communications of the ACM"} MACRO {ibmjrd} {"IBM Journal of Research and Development"} MACRO {ibmsj} {"IBM Systems Journal"} MACRO {ieeese} {"IEEE Transactions on Software Engineering"} MACRO {ieeetc} {"IEEE Transactions on Computers"} MACRO {ieeetcad} {"IEEE Transactions on Computer-Aided Design of Integrated Circuits"} MACRO {ipl} {"Information Processing Letters"} MACRO {jacm} {"Journal of the ACM"} MACRO {jcss} {"Journal of Computer and System Sciences"} MACRO {scp} {"Science of Computer Programming"} MACRO {sicomp} {"SIAM Journal on Computing"} MACRO {tocs} {"ACM Transactions on Computer Systems"} MACRO {tods} {"ACM Transactions on Database Systems"} MACRO {tog} {"ACM Transactions on Graphics"} MACRO {toms} {"ACM Transactions on Mathematical Software"} MACRO {toois} {"ACM Transactions on Office Information Systems"} MACRO {toplas} {"ACM Transactions on Programming Languages and Systems"} MACRO {tcs} {"Theoretical Computer Science"} - READ FUNCTION {sortify} { purify$ "l" change.case$ } INTEGERS { len } FUNCTION {chop.word} { 's := 'len := s #1 len substring$ = { s len #1 + global.max$ substring$ } 's if$ } +% There are three apalike cases: one person (Jones), +% two (Jones and de~Bruijn), and more (Jones et~al.). +% This function is much like format.crossref.editors. +% FUNCTION {format.lab.names} { 's := s #1 "{vv~}{ll}" format.name$ s num.names$ duplicate$ #2 > { pop$ " et~al." * } { #2 < 'skip$ { s #2 "{ff }{vv }{ll}{ jj}" format.name$ "others" = { " et~al." * } { " and " * s #2 "{vv~}{ll}" format.name$ * } if$ } if$ } if$ } FUNCTION {author.key.label} { author empty$ { key empty$ { cite$ #1 #3 substring$ } - 'key + 'key % apalike uses the whole key if$ } { author format.lab.names } if$ } FUNCTION {author.editor.key.label} { author empty$ { editor empty$ { key empty$ { cite$ #1 #3 substring$ } - 'key + 'key % apalike uses the whole key if$ } { editor format.lab.names } if$ } { author format.lab.names } if$ } -FUNCTION {author.key.organization.label} -{ author empty$ - { key empty$ - { organization empty$ - { cite$ #1 #3 substring$ } - { "The " #4 organization chop.word #3 text.prefix$ } - if$ - } - 'key - if$ - } - { author format.lab.names } - if$ -} - -FUNCTION {editor.key.organization.label} +FUNCTION {editor.key.label} { editor empty$ { key empty$ - { organization empty$ - { cite$ #1 #3 substring$ } - { "The " #4 organization chop.word #3 text.prefix$ } - if$ - } - 'key + { cite$ #1 #3 substring$ } + 'key % apalike uses the whole key, no organization if$ } { editor format.lab.names } if$ } -FUNCTION {calc.short.authors} +FUNCTION {calc.label} { type$ "book" = type$ "inbook" = or 'author.editor.key.label { type$ "proceedings" = - 'editor.key.organization.label - { type$ "manual" = - 'author.key.organization.label - 'author.key.label - if$ - } + 'editor.key.label % apalike ignores organization + 'author.key.label % for labeling and sorting if$ } if$ - 'short.list := -} - -FUNCTION {calc.label} -{ calc.short.authors - short.list - "(" - * - year duplicate$ empty$ - short.list key field.or.null = or - { pop$ "" } - 'skip$ - if$ + ", " % these three lines are + * % for apalike, which + year field.or.null purify$ #-1 #4 substring$ % uses all four digits * 'label := } -INTEGERS { seq.num } +FUNCTION {sort.format.names} +{ 's := + #1 'nameptr := + "" + s num.names$ 'numnames := + numnames 'namesleft := + { namesleft #0 > } + { nameptr #1 > + { " " * } + 'skip$ + if$ % apalike uses initials + s nameptr "{vv{ } }{ll{ }}{ f{ }}{ jj{ }}" format.name$ 't := % <= here + nameptr numnames = t "others" = and + { "et al" * } + { t sortify * } + if$ + nameptr #1 + 'nameptr := + namesleft #1 - 'namesleft := + } + while$ +} -FUNCTION {init.seq} -{ #0 'seq.num :=} +FUNCTION {sort.format.title} +{ 't := + "A " #2 + "An " #3 + "The " #4 t chop.word + chop.word + chop.word + sortify + #1 global.max$ substring$ +} -EXECUTE {init.seq} +FUNCTION {author.sort} +{ author empty$ + { key empty$ + { "to sort, need author or key in " cite$ * warning$ + "" + } + { key sortify } + if$ + } + { author sort.format.names } + if$ +} -FUNCTION {int.to.fix} -{ "000000000" swap$ int.to.str$ * - #-1 #10 substring$ +FUNCTION {author.editor.sort} +{ author empty$ + { editor empty$ + { key empty$ + { "to sort, need author, editor, or key in " cite$ * warning$ + "" + } + { key sortify } + if$ + } + { editor sort.format.names } + if$ + } + { author sort.format.names } + if$ } +FUNCTION {editor.sort} +{ editor empty$ + { key empty$ + { "to sort, need editor or key in " cite$ * warning$ + "" + } + { key sortify } + if$ + } + { editor sort.format.names } + if$ +} +% apalike uses two sorting passes; the first one sets the +% labels so that the `a's, `b's, etc. can be computed; +% the second pass puts the references in "correct" order. +% The presort function is for the first pass. It computes +% label, sort.label, and title, and then concatenates. FUNCTION {presort} { calc.label label sortify " " * - seq.num #1 + 'seq.num := - seq.num int.to.fix - 'sort.label := - sort.label * + type$ "book" = + type$ "inbook" = + or + 'author.editor.sort + { type$ "proceedings" = + 'editor.sort + 'author.sort + if$ + } + if$ + #1 entry.max$ substring$ % for + 'sort.label := % apalike + sort.label % style + * + " " + * + title field.or.null + sort.format.title + * #1 entry.max$ substring$ 'sort.key$ := } ITERATE {presort} -SORT +SORT % by label, sort.label, title---for final label calculation -STRINGS { longest.label last.label next.extra } +STRINGS { last.label next.extra } % apalike labels are only for the text; -INTEGERS { longest.label.width last.extra.num number.label } +INTEGERS { last.extra.num } % there are none in the bibliography -FUNCTION {initialize.longest.label} -{ "" 'longest.label := - #0 int.to.chr$ 'last.label := +FUNCTION {initialize.extra.label.stuff} % and hence there is no `longest.label' +{ #0 int.to.chr$ 'last.label := "" 'next.extra := - #0 'longest.label.width := #0 'last.extra.num := - #0 'number.label := } FUNCTION {forward.pass} { last.label label = { last.extra.num #1 + 'last.extra.num := last.extra.num int.to.chr$ 'extra.label := } { "a" chr.to.int$ 'last.extra.num := "" 'extra.label := label 'last.label := } if$ - number.label #1 + 'number.label := } FUNCTION {reverse.pass} { next.extra "b" = { "a" 'extra.label := } 'skip$ if$ - extra.label 'next.extra := - extra.label - duplicate$ empty$ - 'skip$ - { "{\natexlab{" swap$ * "}}" * } - if$ - 'extra.label := label extra.label * 'label := + extra.label 'next.extra := } -EXECUTE {initialize.longest.label} +EXECUTE {initialize.extra.label.stuff} ITERATE {forward.pass} REVERSE {reverse.pass} +% Now that the label is right we sort for real, +% on sort.label then year then title. This is +% for the second sorting pass. FUNCTION {bib.sort.order} -{ sort.label 'sort.key$ := +{ sort.label + " " + * + year field.or.null sortify + * + " " + * + title field.or.null + sort.format.title + * + #1 entry.max$ substring$ + 'sort.key$ := } ITERATE {bib.sort.order} -SORT +SORT % by sort.label, year, title---giving final bibliography order FUNCTION {begin.bib} -{ preamble$ empty$ +{ preamble$ empty$ % no \etalchar in apalike 'skip$ { preamble$ write$ newline$ } if$ - "\begin{thebibliography}{" number.label int.to.str$ * "}" * - write$ newline$ - "\providecommand{\natexlab}[1]{#1}" - write$ newline$ - "\providecommand{\url}[1]{\texttt{#1}}" - write$ newline$ - "\expandafter\ifx\csname urlstyle\endcsname\relax" - write$ newline$ - " \providecommand{\eprint}[1]{eprint: #1}\else" - write$ newline$ - " \providecommand{\eprint}{eprint: \begingroup \urlstyle{rm}\Url}\fi" - write$ newline$ - "\expandafter\ifx\csname urlstyle\endcsname\relax" - write$ newline$ - " \providecommand{\doi}[1]{doi: #1}\else" - write$ newline$ - " \providecommand{\doi}{doi: \begingroup \urlstyle{rm}\Url}\fi" - write$ newline$ + "\begin{thebibliography}{}" write$ newline$ % no labels in apalike } EXECUTE {begin.bib} EXECUTE {init.state.consts} ITERATE {call.type$} FUNCTION {end.bib} { newline$ "\end{thebibliography}" write$ newline$ } EXECUTE {end.bib} diff --git a/docs/theory/examples/axisymmetric_linear-elasticity.py b/docs/theory/examples/axisymmetric_linear-elasticity.py index 0af7a2e..ad4a9c8 100644 --- a/docs/theory/examples/axisymmetric_linear-elasticity.py +++ b/docs/theory/examples/axisymmetric_linear-elasticity.py @@ -1,310 +1,406 @@ +# ================================================================================================== +# +# (c - GPLv3) T.W.J. de Geus (Tom) | tom@geus.me | www.geus.me | github.com/tdegeus/GooseFEM +# +# ================================================================================================== import numpy as np np.set_printoptions(linewidth=200) +# ================================================================================================== +# tensor products +# ================================================================================================== + +ddot22 = lambda A2,B2: np.einsum('...ij ,...ji->... ',A2,B2) +ddot42 = lambda A4,B2: np.einsum('...ijkl,...lk->...ij ',A4,B2) +dyad22 = lambda A2,B2: np.einsum('...ij ,...kl->...ijkl',A2,B2) +ddot43 = lambda A4,B3: np.einsum('...ijkl,...lkm->...ijm',A4,B3) +ddot33 = lambda A3,B3: np.einsum('...ijk ,...kjl->...il ',A3,B3) +dot31 = lambda A3,B1: np.einsum('...ijk ,...k ->...ij ',A3,B1) +transpose3 = lambda A3 : np.einsum('...ijk ->...kji',A3 ) + # ================================================================================================== # mesh definition # ================================================================================================== # number of elements nz = 20 nr = 16 # mesh dimensions nelem = nz * nr # number of elements nnode = (nz+1) * (nr+1) # number of nodes nne = 4 # number of nodes per element -nd = 2 # number of dimensions -ndof = nnode * nd # total number of degrees of freedom +ndim = 2 # number of dimensions +ndof = nnode * ndim # total number of degrees of freedom -# coordinates and connectivity: zero-initialize -coor = np.zeros((nnode,nd ), dtype='float') -conn = np.zeros((nelem,nne), dtype='int' ) +# zero-initialise coordinates, displacements, and connectivity +coor = np.zeros((nnode,ndim), dtype='float') +disp = np.zeros((nnode,ndim), dtype='float') +conn = np.zeros((nelem,nne ), dtype='int' ) -# coordinates: set +# coordinates # - grid of points z,r = np.meshgrid(np.linspace(0,1,nz+1), np.linspace(0,1,nr+1)) # - store from grid of points coor[:,0] = z.ravel() coor[:,1] = r.ravel() -# connectivity: set +# connectivity # - grid of node numbers inode = np.arange(nnode).reshape(nr+1, nz+1) # - store from grid of node numbers conn[:,0] = inode[ :-1, :-1].ravel() conn[:,1] = inode[ :-1,1: ].ravel() conn[:,2] = inode[1: ,1: ].ravel() conn[:,3] = inode[1: , :-1].ravel() +# - node sets +nodesLeft = inode[ :, 0] +nodesRight = inode[ :,-1] +nodesBottom = inode[ 0, :] +nodesTop = inode[-1, :] # DOF-numbers per node -dofs = np.arange(nnode*nd).reshape(nnode,nd) +dofs = np.arange(nnode*ndim).reshape(nnode,ndim) # ================================================================================================== # quadrature definition # ================================================================================================== # integration point coordinates (local element coordinates) Xi = np.array([ [-1./np.sqrt(3.), -1./np.sqrt(3.)], [+1./np.sqrt(3.), -1./np.sqrt(3.)], [+1./np.sqrt(3.), +1./np.sqrt(3.)], [-1./np.sqrt(3.), +1./np.sqrt(3.)], ]) # integration point weights W = np.array([ [1.], [1.], [1.], [1.], ]) # number of integration points nip = 4 # ================================================================================================== -# integration point tensors and operations +# B-matrix at each integration point # ================================================================================================== -# tensors products -ddot22 = lambda A2,B2: np.einsum('...ij ,...ji->... ',A2,B2) -ddot42 = lambda A4,B2: np.einsum('...ijkl,...lk->...ij ',A4,B2) -dyad22 = lambda A2,B2: np.einsum('...ij ,...kl->...ijkl',A2,B2) -ddot43 = lambda A4,B3: np.einsum('...ijkl,...lkm->...ijm',A4,B3) -ddot33 = lambda A3,B3: np.einsum('...ijk ,...kjl->...il ',A3,B3) -dot31 = lambda A3,B1: np.einsum('...ijk ,...k ->...ij ',A3,B1) -transpose3 = lambda A3 : np.einsum('...ijk ->...kji',A3 ) - -# identity tensors (per integration point) -i = np.eye(3) -I = np.einsum('ij ,...' , i ,np.ones([nelem,nip])) -I4 = np.einsum('ijkl,...->...ijkl',np.einsum('il,jk',i,i),np.ones([nelem,nip])) -I4rt = np.einsum('ijkl,...->...ijkl',np.einsum('ik,jl',i,i),np.ones([nelem,nip])) -I4s = (I4+I4rt)/2. -II = dyad22(I,I) -I4d = I4s-II/3. - -# bulk and shear modulus -K = 0.8333333333333333 -G = 0.3846153846153846 - -# elasticity tensor (per integration point) -C4 = K * II + 2. * G * I4d - -# ================================================================================================== -# B-matrices at each integration point -# ================================================================================================== - -# allocate matrix -B = np.zeros((nelem,nip,nne,3,3,3)) -vol = np.zeros((nelem,nip)) +# allocate +B = np.empty((nelem,nip,nne,3,3,3)) +vol = np.empty((nelem,nip)) # loop over nodes for e, nodes in enumerate(conn): # nodal coordinates xe = coor[nodes,:] # loop over integration points for q, (w, xi) in enumerate(zip(W, Xi)): # shape functions N = np.array([ .25 * (1.-xi[0]) * (1.-xi[1]), .25 * (1.+xi[0]) * (1.-xi[1]), .25 * (1.+xi[0]) * (1.+xi[1]), .25 * (1.-xi[0]) * (1.+xi[1]), ]) # shape function gradients (w.r.t. the local element coordinates) dNdxi = np.array([ [-.25*(1.-xi[1]), -.25*(1.-xi[0])], [+.25*(1.-xi[1]), -.25*(1.+xi[0])], [+.25*(1.+xi[1]), +.25*(1.+xi[0])], [-.25*(1.+xi[1]), +.25*(1.-xi[0])], ]) # Jacobian - J = np.einsum('mi,mj->ij', dNdxi, xe) - Jinv = np.linalg.inv(J) - Jdet = np.linalg.det(J) + Je = np.einsum('mi,mj->ij', dNdxi, xe) + invJe = np.linalg.inv(Je) + detJe = np.linalg.det(Je) # shape function gradients (w.r.t. the global coordinates) - dNdx = np.einsum('ij,mj->mi', Jinv, dNdxi) + dNdxe = np.einsum('ij,mj->mi', invJe, dNdxi) - # global coordinates of the integration point + # global coordinates of the integration point, extract the radius xq = np.einsum('m,mi->i', N, xe) rq = xq[1] # compute B-matrix and integration-point volume and store for later use for m in range(nne): - Bm = np.zeros((3,3,3)) + Be = np.zeros((3,3,3)) - Bm[0,1,1] = -1./rq * N[m] # B(m, r \theta \theta ) - Bm[1,1,0] = +1./rq * N[m] # B(m, \theta \theta r ) + Be[0,0,0] = dNdxe[m,1] # B(m, r r r ) + Be[0,2,2] = dNdxe[m,1] # B(m, r z z ) + Be[1,1,0] = +1./rq * N[m] # B(m, \theta \theta r ) + Be[2,0,0] = dNdxe[m,0] # B(m, z r r ) + Be[2,2,2] = dNdxe[m,0] # B(m, z z z ) - Bm[0,0,0] = dNdx[m,1] # B(m, r r r ) - Bm[0,1,1] = dNdx[m,1] # B(m, r \theta \theta ) - Bm[0,2,2] = dNdx[m,1] # B(m, r z z ) + B[e,q,m,:,:,:] = Be - Bm[2,0,0] = dNdx[m,0] # B(m, z r r ) - Bm[2,1,1] = dNdx[m,0] # B(m, z \theta \theta ) - Bm[2,2,2] = dNdx[m,0] # B(m, z z z ) + vol[e,q] = w * detJe * rq * 2. * np.pi - B[e,q,m,:,:,:] = Bm +# ================================================================================================== +# stiffness tensor at each integration point (provides constitutive response and 'tangent') +# ================================================================================================== - vol[e,q] = w * Jdet * rq * 2. * np.pi +# identity tensors (per integration point) +i = np.eye(3) +I = np.einsum('ij ,...' , i ,np.ones([nelem,nip])) +I4 = np.einsum('ijkl,...->...ijkl',np.einsum('il,jk',i,i),np.ones([nelem,nip])) +I4rt = np.einsum('ijkl,...->...ijkl',np.einsum('ik,jl',i,i),np.ones([nelem,nip])) +I4s = (I4+I4rt)/2. +II = dyad22(I,I) +I4d = I4s-II/3. + +# bulk and shear modulus (homogeneous) +K = 0.8333333333333333 * np.ones((nelem, nip)) +G = 0.3846153846153846 * np.ones((nelem, nip)) + +# elasticity tensor (per integration point) +C4 = np.einsum('eq,eqijkl->eqijkl', K, II) + 2. * np.einsum('eq,eqijkl->eqijkl', G, I4d) # ================================================================================================== -# assemble stiffness matrix +# strain from nodal displacements, stress from constitutive response # ================================================================================================== -# allocate matrix -K = np.zeros((ndof, ndof)) +# allocate strain tensor per integration point +Eps = np.empty((nelem,nip,3,3)) # loop over nodes for e, nodes in enumerate(conn): - # allocate element stiffness matrix - Ke = np.zeros((nne*nd, nne*nd)) + # nodal displacements in 3-d + # u_theta = 0 + # (z,r) -> (r,theta,z) + ue = np.zeros((nne,3)) + ue[:,0] = disp[nodes,1] + ue[:,2] = disp[nodes,0] # loop over integration points for q, (w, xi) in enumerate(zip(W, Xi)): # alias integration point values - C4q = C4[e,q,:,:,:,:] - dV = vol[e,q] + Be = B[e,q,:,:,:,:] - # assemble to element stiffness matrix - for m in range(nne): + # displacement gradient + gradu = np.einsum('mijk,mk->ij', Be, ue) + + # compute strain tensor, and store per integration point + Eps[e,q] = .5 * ( gradu + gradu.T ) + +# constitutive response: strain tensor -> stress tensor (per integration point) +Sig = ddot42(C4, Eps) - Bm = B[e,q,m,:,:,:] +# ================================================================================================== +# internal force from stress +# ================================================================================================== - for n in range(nne): +# allocate internal force +fint = np.zeros((ndof)) + +# loop over nodes +for e, nodes in enumerate(conn): - Bn = B[e,q,n,:,:,:] + # allocate element internal force + fe = np.zeros((nne*ndim)) - Kmn = ddot33(transpose3(Bm),ddot43(C4q,Bn)) + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): - iim = np.array([ m*nd+0, m*nd+1 ]) - iin = np.array([ n*nd+0, n*nd+1 ]) + # alias integration point values + sig = Sig[e,q,:,:] + Be = B [e,q,:,:,:,:] + dV = vol[e,q] - Ke[np.ix_(iim,iin)] += Kmn[np.ix_([2,0], [2,0])] * dV + # assemble to element internal force + # Bm = B[e,q,m,:,:,:] + # fm = ddot32(transpose3(Bm), sig) * dV + # fe[[m*ndim+0, m*ndim+1]] = [fm[m,2], fm[m,0]] + fq = np.einsum('mijk,ij->mk', Be, sig) * dV + fe += fq[:,[2,0]].reshape(nne*ndim) # assemble to global stiffness matrix iie = dofs[nodes,:].ravel() + fint[iie] += fe + +# ================================================================================================== +# stiffness matrix from 'tangent' +# ================================================================================================== +# allocate stiffness matrix +K = np.zeros((ndof, ndof)) + +# loop over nodes +for e, nodes in enumerate(conn): + + # allocate element stiffness matrix + Ke = np.zeros((nne*ndim, nne*ndim)) + + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): + + # alias integration point values + c4 = C4 [e,q,:,:,:,:] + Be = B [e,q,:,:,:,:] + dV = vol[e,q] + + # assemble to element stiffness matrix + # Bm = B[e,q,m,:,:,:] + # Bn = B[e,q,n,:,:,:] + # Kmn = ddot33(transpose3(Bm),ddot43(C4,Bn)) * dV + # Ke[[m*ndim+0,m*ndim+1], [n*ndim+0,n*ndim+1]] += Kmn[[2,0], [2,0]] + Kq = np.einsum('mabc,abde,nedf->mcnf', Be, c4, Be) * dV + Ke += Kq[np.ix_(np.arange(nne),[2,0],np.arange(nne),[2,0])].reshape(nne*ndim, nne*ndim) + + # assemble to global stiffness matrix + iie = dofs[nodes,:].ravel() K[np.ix_(iie,iie)] += Ke # ================================================================================================== # partition and solve # ================================================================================================== -# zero-initialize displacements and forces -f = np.zeros((ndof)) -u = np.zeros((ndof)) +# prescribed external force: zero on all free DOFs +# (other DOFs ignored in solution, the reaction forces on the DOFs are computed below) +fext = np.zeros((ndof)) + +# DOF-partitioning: ['u'nknown, 'p'rescribed] +# - prescribed +iip = np.hstack(( + dofs[nodesBottom,1], + dofs[nodesLeft ,0], + dofs[nodesRight ,0], +)) +# - unknown +iiu = np.setdiff1d(dofs.ravel(), iip) # fixed displacements -# - zero-initialize -iip = np.empty((0),dtype='int' ) -up = np.empty((0),dtype='float') -# - 'r = 0' : 'u_r = 0' -idof = dofs[inode[0,:], 1] -iip = np.hstack(( iip, idof )) -up = np.hstack(( up , 0.0 * np.ones(idof.shape) )) -# - 'z = 0' : 'u_z = 0' -idof = dofs[inode[:,0], 0] -iip = np.hstack(( iip, idof )) -up = np.hstack(( up , 0.0 * np.ones(idof.shape) )) -# - 'z = 1' : 'u_z = 0.1' -idof = dofs[inode[:,-1], 0] -iip = np.hstack(( iip, idof )) -up = np.hstack(( up , 0.1 * np.ones(idof.shape) )) - -# free displacements -iiu = np.setdiff1d(dofs.ravel(), iip) +up = np.hstack(( + 0.0 * np.ones((len(nodesBottom))), + 0.0 * np.ones((len(nodesLeft ))), + 0.1 * np.ones((len(nodesRight ))), +)) + +# residual force +r = fext - fint # partition # - stiffness matrix -Kuu = K[np.ix_(iiu, iiu)] -Kup = K[np.ix_(iiu, iip)] -Kpu = K[np.ix_(iip, iiu)] -Kpp = K[np.ix_(iip, iip)] -# - external force -fu = f[iiu] +Kuu = K[np.ix_(iiu, iiu)] +Kup = K[np.ix_(iiu, iip)] +Kpu = K[np.ix_(iip, iiu)] +Kpp = K[np.ix_(iip, iip)] +# - residual force +ru = r[iiu] -# solve -uu = np.linalg.solve(Kuu, fu - Kup.dot(up)) -fp = Kpu.dot(uu) + Kpp.dot(up) +# solve for unknown displacement DOFs +uu = np.linalg.solve(Kuu, ru - Kup.dot(up)) # assemble +u = np.empty((ndof)) u[iiu] = uu u[iip] = up -f[iip] = fp -# convert to nodal displacement and forces +# convert to nodal displacements disp = u[dofs] -fext = f[dofs] # ================================================================================================== -# compute strain and stress +# strain from nodal displacements, stress from constitutive response # ================================================================================================== -# zero-initialize -# - strain tensor per integration point -eps = np.zeros((nelem,nip,3,3)) -# - nodal displacement in 3-d -um = np.zeros((3)) +# allocate strain tensor per integration point +Eps = np.empty((nelem,nip,3,3)) # loop over nodes for e, nodes in enumerate(conn): - # nodal displacements - ue = disp[nodes,:] + # nodal displacements in 3-d + # u_theta = 0 + # (z,r) -> (r,theta,z) + ue = np.zeros((nne,3)) + ue[:,0] = disp[nodes,1] + ue[:,2] = disp[nodes,0] # loop over integration points for q, (w, xi) in enumerate(zip(W, Xi)): - # zero-initialize displacements gradient - gradu = np.zeros((3,3)) + # alias integration point values + Be = B[e,q,:,:,:,:] - # compute displacement gradient - for m in range(nne): + # displacement gradient + gradu = np.einsum('mijk,mk->ij', Be, ue) - # alias - # - B-matrix - Bm = B[e,q,m,:,:,:] - # - nodal displacement in 3-d (theta-component always zero) - um[np.ix_([2,0])] = ue[m,:] + # compute strain tensor, and store per integration point + Eps[e,q] = .5 * ( gradu + gradu.T ) - # update - gradu += dot31(Bm, um) +# constitutive response: strain tensor -> stress tensor (per integration point) +Sig = ddot42(C4, Eps) - # compute strain tensor, and store per integration point - eps[e,q] = .5 * ( gradu + gradu.T ) +# ================================================================================================== +# internal force from stress, reaction (external) force from internal force on fixed nodes +# ================================================================================================== -# compute stress tensor (per integration point) -sig = ddot42(C4,eps) +# allocate internal force +fint = np.zeros((ndof)) + +# loop over nodes +for e, nodes in enumerate(conn): + + # allocate element internal force + fe = np.zeros((nne*ndim)) + + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): + + # alias integration point values + sig = Sig[e,q,:,:] + Be = B [e,q,:,:,:,:] + dV = vol[e,q] + + # assemble to element internal force + # Bm = B[e,q,m,:,:,:] + # fm = ddot32(transpose3(Bm), sig) * dV + # fe[[m*ndim+0, m*ndim+1]] = [fm[m,2], fm[m,0]] + fq = np.einsum('mijk,ij->mk', Be, sig) * dV + fe += fq[:,[2,0]].reshape(nne*ndim) + + # assemble to global stiffness matrix + iie = dofs[nodes,:].ravel() + fint[iie] += fe + +# reaction force +fext[iip] = fint[iip] # ================================================================================================== # plot # ================================================================================================== import matplotlib.pyplot as plt -fig, axes = plt.subplots(ncols=2, figsize=(16,8)) +fig, axes = plt.subplots(ncols=2, figsize=(16,8)) + +# reconstruct external force as nodal vectors +fext = fext[dofs] +# plot original nodal positions + displacement field as arrows axes[0].scatter(coor[:,0], coor[:,1]) axes[0].quiver (coor[:,0], coor[:,1], disp[:,0], disp[:,1]) +# plot new nodal positions + external (reaction) force field as arrows axes[1].scatter((coor+disp)[:,0], (coor+disp)[:,1]) axes[1].quiver ((coor+disp)[:,0], (coor+disp)[:,1], fext[:,0], fext[:,1], scale=.4) +# fix axes limits for axis in axes: axis.set_xlim([-0.4, 1.5]) axis.set_ylim([-0.4, 1.5]) plt.show() diff --git a/docs/theory/examples/cartesian_linear-elasticity.py b/docs/theory/examples/cartesian_linear-elasticity.py index 7ab7124..f0e11e1 100644 --- a/docs/theory/examples/cartesian_linear-elasticity.py +++ b/docs/theory/examples/cartesian_linear-elasticity.py @@ -1,239 +1,370 @@ - -import numpy as np - -np.set_printoptions(linewidth=200) - # ================================================================================================== -# identity tensors +# +# (c - GPLv3) T.W.J. de Geus (Tom) | tom@geus.me | www.geus.me | github.com/tdegeus/GooseFEM +# # ================================================================================================== -II = np.zeros((3,3,3,3)) -I4 = np.zeros((3,3,3,3)) -I4rt = np.zeros((3,3,3,3)) -Is = np.zeros((3,3,3,3)) - -for i in range(3): - for j in range(3): - for k in range(3): - for l in range(3): - if ( i == j and k == l ): - II[i,j,k,l] = 1. - -for i in range(3): - for j in range(3): - for k in range(3): - for l in range(3): - if ( i == l and j == k ): - I4[i,j,k,l] = 1. - -for i in range(3): - for j in range(3): - for k in range(3): - for l in range(3): - if ( i == k and j == l ): - I4rt[i,j,k,l] = 1. +import numpy as np -I4s = ( I4 + I4rt ) / 2. -I4d = ( I4s - II/3. ) +np.set_printoptions(linewidth=200) # ================================================================================================== -# elasticity tensor +# tensor products # ================================================================================================== -# bulk and shear modulus -K = 0.8333333333333333 -G = 0.3846153846153846 - -# elasticity tensor (3d) -C4 = K * II + 2. * G * I4d +ddot22 = lambda A2,B2: np.einsum('...ij ,...ji->... ',A2,B2) +ddot42 = lambda A4,B2: np.einsum('...ijkl,...lk->...ij ',A4,B2) +dyad22 = lambda A2,B2: np.einsum('...ij ,...kl->...ijkl',A2,B2) # ================================================================================================== # mesh definition # ================================================================================================== # number of elements -nx = 10 -ny = 10 +nx = 20 +ny = 20 # mesh dimensions nelem = nx * ny # number of elements nnode = (nx+1) * (ny+1) # number of nodes nne = 4 # number of nodes per element -nd = 2 # number of dimensions -ndof = nnode * nd # total number of degrees of freedom +ndim = 2 # number of dimensions +ndof = nnode * ndim # total number of degrees of freedom # out-of-plane thickness thick = 1. -# coordinates and connectivity: zero-initialize -coor = np.zeros((nnode,nd ), dtype='float') -conn = np.zeros((nelem,nne), dtype='int' ) +# zero-initialise coordinates, displacements, and connectivity +coor = np.zeros((nnode,ndim), dtype='float') +disp = np.zeros((nnode,ndim), dtype='float') +conn = np.zeros((nelem,nne ), dtype='int' ) -# coordinates: set +# coordinates # - grid of points x,y = np.meshgrid(np.linspace(0,1,nx+1), np.linspace(0,1,ny+1)) # - store from grid of points coor[:,0] = x.ravel() coor[:,1] = y.ravel() -# connectivity: set +# connectivity # - grid of node numbers inode = np.arange(nnode).reshape(ny+1, nx+1) # - store from grid of node numbers conn[:,0] = inode[ :-1, :-1].ravel() conn[:,1] = inode[ :-1,1: ].ravel() conn[:,2] = inode[1: ,1: ].ravel() conn[:,3] = inode[1: , :-1].ravel() +# - node sets +nodesLeft = inode[ :, 0] +nodesRight = inode[ :,-1] +nodesBottom = inode[ 0, :] +nodesTop = inode[-1, :] # DOF-numbers per node -dofs = np.arange(nnode*nd).reshape(nnode,nd) +dofs = np.arange(nnode*ndim).reshape(nnode,ndim) # ================================================================================================== # quadrature definition # ================================================================================================== # integration point coordinates (local element coordinates) Xi = np.array([ [-1./np.sqrt(3.), -1./np.sqrt(3.)], [+1./np.sqrt(3.), -1./np.sqrt(3.)], [+1./np.sqrt(3.), +1./np.sqrt(3.)], [-1./np.sqrt(3.), +1./np.sqrt(3.)], ]) # integration point weights W = np.array([ [1.], [1.], [1.], [1.], ]) +# number of integration points +nip = 4 + # ================================================================================================== -# assemble stiffness matrix +# gradient of the shape functions at each integration point # ================================================================================================== -# allocate matrix -K = np.zeros((ndof, ndof)) +# allocate +dNx = np.empty((nelem,nip,nne,ndim)) +vol = np.empty((nelem,nip)) # loop over nodes -for e in conn: +for e, nodes in enumerate(conn): - # - nodal coordinates - xe = coor[e,:] + # nodal coordinates + xe = coor[nodes,:] - # - allocate element stiffness matrix - Ke = np.zeros((nne*nd, nne*nd)) + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): - # - numerical quadrature - for w, xi in zip(W, Xi): - - # -- shape function gradients (w.r.t. the local element coordinates) + # shape function gradients (w.r.t. the local element coordinates) dNdxi = np.array([ [-.25*(1.-xi[1]), -.25*(1.-xi[0])], [+.25*(1.-xi[1]), -.25*(1.+xi[0])], [+.25*(1.+xi[1]), +.25*(1.+xi[0])], [-.25*(1.+xi[1]), +.25*(1.-xi[0])], ]) - # -- Jacobian - J = np.zeros((nd, nd)) + # Jacobian + Je = np.einsum('mi,mj->ij', dNdxi, xe) + invJe = np.linalg.inv(Je) + detJe = np.linalg.det(Je) + + # shape function gradients (w.r.t. the global coordinates) + dNdxe = np.einsum('ij,mj->mi', invJe, dNdxi) + + # store for later use + dNx[e,q,:,:] = dNdxe + vol[e,q] = w * detJe * thick + +# ================================================================================================== +# stiffness tensor at each integration point (provides constitutive response and 'tangent') +# ================================================================================================== + +# identity tensors (per integration point) +i = np.eye(3) +I = np.einsum('ij ,...' , i ,np.ones([nelem,nip])) +I4 = np.einsum('ijkl,...->...ijkl',np.einsum('il,jk',i,i),np.ones([nelem,nip])) +I4rt = np.einsum('ijkl,...->...ijkl',np.einsum('ik,jl',i,i),np.ones([nelem,nip])) +I4s = (I4+I4rt)/2. +II = dyad22(I,I) +I4d = I4s-II/3. - for m in range(nne): - for i in range(nd): - for j in range(nd): - J[i,j] += dNdxi[m,i] * xe[m,j] +# bulk and shear modulus (homogeneous) +K = 0.8333333333333333 * np.ones((nelem, nip)) +G = 0.3846153846153846 * np.ones((nelem, nip)) + +# elasticity tensor (per integration point) +C4 = np.einsum('eq,eqijkl->eqijkl', K, II) + 2. * np.einsum('eq,eqijkl->eqijkl', G, I4d) + +# ================================================================================================== +# strain from nodal displacements, stress from constitutive response +# ================================================================================================== + +# zero-initialise strain tensor per integration point +# (plain strain -> all z-components are not written and should remain zero at all times) +Eps = np.zeros((nelem,nip,3,3)) + +# loop over nodes +for e, nodes in enumerate(conn): + + # nodal displacements + ue = disp[nodes,:] + + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): + + # alias integration point values + dNdxe = dNx[e,q,:,:] + + # displacement gradient + gradu = np.einsum('mi,mj->ij', dNdxe, ue) + + # compute strain tensor, and store per integration point + # (use plain strain to convert 2-d to 3-d tensor) + Eps[e,q,:2,:2] = .5 * ( gradu + gradu.T ) + +# constitutive response: strain tensor -> stress tensor (per integration point) +Sig = ddot42(C4, Eps) + +# ================================================================================================== +# internal force from stress +# ================================================================================================== + +# allocate internal force +fint = np.zeros((ndof)) + +# loop over nodes +for e, nodes in enumerate(conn): - Jinv = np.linalg.inv(J) - Jdet = np.linalg.det(J) + # allocate element internal force + fe = np.zeros((nne*ndim)) - # -- shape function gradients (w.r.t. the global coordinates) - dNdx = np.zeros((nne,nd)) + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): - for m in range(nne): - for i in range(nd): - for j in range(nd): - dNdx[m,i] += Jinv[i,j] * dNdxi[m,j] + # alias integration point values + # (plane strain: stress in z-direction irrelevant) + sig = Sig[e,q,:2,:2] + dNdxe = dNx[e,q,:,:] + dV = vol[e,q] - # -- assemble to element stiffness matrix - for m in range(nne): - for n in range(nne): - for i in range(nd): - for j in range(nd): - for k in range(nd): - for l in range(nd): - Ke[m*nd+j, n*nd+k] += dNdx[m,i] * C4[i,j,k,l] * dNdx[n,l] * w * Jdet * thick + # assemble to element internal force + # fe[m*nd+j] += dNdx[m,i] * sig[i,j] * dV + fe += ( np.einsum('mi,ij->mj', dNdxe, sig) * dV ).reshape(nne*ndim) - # - assemble to global stiffness matrix - iie = dofs[e,:].ravel() + # assemble to global stiffness matrix + iie = dofs[nodes,:].ravel() + fint[iie] += fe +# ================================================================================================== +# stiffness matrix from 'tangent' +# ================================================================================================== + +# allocate stiffness matrix +K = np.zeros((ndof, ndof)) + +# loop over nodes +for e, nodes in enumerate(conn): + + # allocate element stiffness matrix + Ke = np.zeros((nne*ndim, nne*ndim)) + + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): + + # alias integration point values + c4 = C4 [e,q,:2,:2,:2,:2] + dNdxe = dNx[e,q,:,:] + dV = vol[e,q] + + # assemble to element stiffness matrix + # Ke[m*nd+j, n*nd+k] += dNdx[m,i] * C4[i,j,k,l] * dNdx[n,l] * dV + Ke += ( np.einsum('mi,ijkl,nl->mjnk', dNdxe, c4, dNdxe) * dV ).reshape(nne*ndim, nne*ndim) + + # assemble to global stiffness matrix + iie = dofs[nodes,:].ravel() K[np.ix_(iie,iie)] += Ke # ================================================================================================== # partition and solve # ================================================================================================== -# zero-initialize displacements and forces -f = np.zeros((ndof)) -u = np.zeros((ndof)) +# prescribed external force: zero on all free DOFs +# (other DOFs ignored in solution, the reaction forces on the DOFs are computed below) +fext = np.zeros((ndof)) + +# DOF-partitioning: ['u'nknown, 'p'rescribed] +# - prescribed +iip = np.hstack(( + dofs[nodesBottom,1], + dofs[nodesLeft ,0], + dofs[nodesRight ,0], +)) +# - unknown +iiu = np.setdiff1d(dofs.ravel(), iip) # fixed displacements -# - zero-initialize -iip = np.empty((0),dtype='int' ) -up = np.empty((0),dtype='float') -# - 'y = 0' : 'u_y = 0' -idof = dofs[inode[0,:], 1] -iip = np.hstack(( iip, idof )) -up = np.hstack(( up , 0.0 * np.ones(idof.shape) )) -# - 'x = 0' : 'u_x = 0' -idof = dofs[inode[:,0], 0] -iip = np.hstack(( iip, idof )) -up = np.hstack(( up , 0.0 * np.ones(idof.shape) )) -# - 'x = 1' : 'u_x = 0.1' -idof = dofs[inode[:,-1], 0] -iip = np.hstack(( iip, idof )) -up = np.hstack(( up , 0.1 * np.ones(idof.shape) )) - -# free displacements -iiu = np.setdiff1d(dofs.ravel(), iip) +up = np.hstack(( + 0.0 * np.ones((len(nodesBottom))), + 0.0 * np.ones((len(nodesLeft ))), + 0.1 * np.ones((len(nodesRight ))), +)) + +# residual force +r = fext - fint # partition # - stiffness matrix -Kuu = K[np.ix_(iiu, iiu)] -Kup = K[np.ix_(iiu, iip)] -Kpu = K[np.ix_(iip, iiu)] -Kpp = K[np.ix_(iip, iip)] -# - external force -fu = f[iiu] +Kuu = K[np.ix_(iiu, iiu)] +Kup = K[np.ix_(iiu, iip)] +Kpu = K[np.ix_(iip, iiu)] +Kpp = K[np.ix_(iip, iip)] +# - residual force +ru = r[iiu] -# solve -uu = np.linalg.solve(Kuu, fu - Kup.dot(up)) -fp = Kpu.dot(uu) + Kpp.dot(up) +# solve for unknown displacement DOFs +uu = np.linalg.solve(Kuu, ru - Kup.dot(up)) # assemble +u = np.empty((ndof)) u[iiu] = uu u[iip] = up -f[iip] = fp -# convert to nodal displacement and forces +# convert to nodal displacements disp = u[dofs] -fext = f[dofs] + +# ================================================================================================== +# strain from nodal displacements, stress from constitutive response +# ================================================================================================== + +# zero-initialise strain tensor per integration point +# (plain strain -> all z-components are not written and should remain zero at all times) +Eps = np.zeros((nelem,nip,3,3)) + +# loop over nodes +for e, nodes in enumerate(conn): + + # nodal displacements + ue = disp[nodes,:] + + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): + + # alias integration point values + dNdxe = dNx[e,q,:,:] + + # displacement gradient + gradu = np.einsum('mi,mj->ij', dNdxe, ue) + + # compute strain tensor, and store per integration point + # (use plain strain to convert 2-d to 3-d tensor) + Eps[e,q,:2,:2] = .5 * ( gradu + gradu.T ) + +# constitutive response: strain tensor -> stress tensor (per integration point) +Sig = ddot42(C4, Eps) + +# ================================================================================================== +# internal force from stress, reaction (external) force from internal force on fixed nodes +# ================================================================================================== + +# allocate internal force +fint = np.zeros((ndof)) + +# loop over nodes +for e, nodes in enumerate(conn): + + # allocate element internal force + fe = np.zeros((nne*ndim)) + + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): + + # alias integration point values + # (plane strain: stress in z-direction irrelevant) + sig = Sig[e,q,:2,:2] + dNdxe = dNx[e,q,:,:] + dV = vol[e,q] + + # assemble to element internal force + # fe[m*nd+j] += dNdx[m,i] * sig[i,j] * dV + fe += ( np.einsum('mi,ij->mj', dNdxe, sig) * dV ).reshape(nne*ndim) + + # assemble to global stiffness matrix + iie = dofs[nodes,:].ravel() + fint[iie] += fe + +# reaction force +fext[iip] = fint[iip] # ================================================================================================== # plot # ================================================================================================== import matplotlib.pyplot as plt -fig, axes = plt.subplots(ncols=2, figsize=(16,8)) +fig, axes = plt.subplots(ncols=2, figsize=(16,8)) + +# reconstruct external force as nodal vectors +fext = fext[dofs] +# plot original nodal positions + displacement field as arrows axes[0].scatter(coor[:,0], coor[:,1]) axes[0].quiver (coor[:,0], coor[:,1], disp[:,0], disp[:,1]) +# plot new nodal positions + external (reaction) force field as arrows axes[1].scatter((coor+disp)[:,0], (coor+disp)[:,1]) axes[1].quiver ((coor+disp)[:,0], (coor+disp)[:,1], fext[:,0], fext[:,1], scale=.1) +# fix axes limits for axis in axes: axis.set_xlim([-0.4, 1.5]) axis.set_ylim([-0.4, 1.5]) plt.show() diff --git a/docs/theory/examples/cartesian_linear-elasticity_B-matrix.py b/docs/theory/examples/cartesian_linear-elasticity_B-matrix.py index 2fb964e..fb7e9dd 100644 --- a/docs/theory/examples/cartesian_linear-elasticity_B-matrix.py +++ b/docs/theory/examples/cartesian_linear-elasticity_B-matrix.py @@ -1,308 +1,392 @@ +# ================================================================================================== +# +# (c - GPLv3) T.W.J. de Geus (Tom) | tom@geus.me | www.geus.me | github.com/tdegeus/GooseFEM +# +# ================================================================================================== import numpy as np np.set_printoptions(linewidth=200) # ================================================================================================== # tensor products # ================================================================================================== -def ddot43(A,B): - - nd = A.shape[0] - - C = np.zeros((nd,nd,nd)) - - for i in range(nd): - for j in range(nd): - for k in range(nd): - for l in range(nd): - for m in range(nd): - C[i,j,m] += A[i,j,k,l] * B[l,k,m] - - return C - -# -------------------------------------------------------------------------------------------------- - -def ddot33(A,B): - - nd = A.shape[0] - - C = np.zeros((nd,nd)) - - for i in range(nd): - for j in range(nd): - for k in range(nd): - for l in range(nd): - C[i,l] += A[i,j,k] * B[k,j,l] - - return C - -# -------------------------------------------------------------------------------------------------- - -def transpose3(A): - - nd = A.shape[0] - - C = np.zeros((nd,nd,nd)) - - for i in range(nd): - for j in range(nd): - for k in range(nd): - C[k,j,i] = A[i,j,k] - - return C - -# ================================================================================================== -# identity tensors -# ================================================================================================== - -II = np.zeros((3,3,3,3)) -I4 = np.zeros((3,3,3,3)) -I4rt = np.zeros((3,3,3,3)) -Is = np.zeros((3,3,3,3)) - -for i in range(3): - for j in range(3): - for k in range(3): - for l in range(3): - if ( i == j and k == l ): - II[i,j,k,l] = 1. - -for i in range(3): - for j in range(3): - for k in range(3): - for l in range(3): - if ( i == l and j == k ): - I4[i,j,k,l] = 1. - -for i in range(3): - for j in range(3): - for k in range(3): - for l in range(3): - if ( i == k and j == l ): - I4rt[i,j,k,l] = 1. - -I4s = ( I4 + I4rt ) / 2. -I4d = ( I4s - II/3. ) - -# ================================================================================================== -# elasticity tensor -# ================================================================================================== - -# bulk and shear modulus -K = 0.8333333333333333 -G = 0.3846153846153846 - -# elasticity tensor (3d) -C4 = K * II + 2. * G * I4d +ddot22 = lambda A2,B2: np.einsum('...ij ,...ji->... ',A2,B2) +ddot42 = lambda A4,B2: np.einsum('...ijkl,...lk->...ij ',A4,B2) +dyad22 = lambda A2,B2: np.einsum('...ij ,...kl->...ijkl',A2,B2) # ================================================================================================== # mesh definition # ================================================================================================== # number of elements -nx = 10 -ny = 10 +nx = 20 +ny = 20 # mesh dimensions nelem = nx * ny # number of elements nnode = (nx+1) * (ny+1) # number of nodes nne = 4 # number of nodes per element -nd = 2 # number of dimensions -ndof = nnode * nd # total number of degrees of freedom +ndim = 2 # number of dimensions +ndof = nnode * ndim # total number of degrees of freedom # out-of-plane thickness thick = 1. -# coordinates and connectivity: zero-initialize -coor = np.zeros((nnode,nd ), dtype='float') -conn = np.zeros((nelem,nne), dtype='int' ) +# zero-initialise coordinates, displacements, and connectivity +coor = np.zeros((nnode,ndim), dtype='float') +disp = np.zeros((nnode,ndim), dtype='float') +conn = np.zeros((nelem,nne ), dtype='int' ) -# coordinates: set +# coordinates # - grid of points x,y = np.meshgrid(np.linspace(0,1,nx+1), np.linspace(0,1,ny+1)) # - store from grid of points coor[:,0] = x.ravel() coor[:,1] = y.ravel() -# connectivity: set +# connectivity # - grid of node numbers inode = np.arange(nnode).reshape(ny+1, nx+1) # - store from grid of node numbers conn[:,0] = inode[ :-1, :-1].ravel() conn[:,1] = inode[ :-1,1: ].ravel() conn[:,2] = inode[1: ,1: ].ravel() conn[:,3] = inode[1: , :-1].ravel() +# - node sets +nodesLeft = inode[ :, 0] +nodesRight = inode[ :,-1] +nodesBottom = inode[ 0, :] +nodesTop = inode[-1, :] # DOF-numbers per node -dofs = np.arange(nnode*nd).reshape(nnode,nd) +dofs = np.arange(nnode*ndim).reshape(nnode,ndim) # ================================================================================================== # quadrature definition # ================================================================================================== # integration point coordinates (local element coordinates) Xi = np.array([ [-1./np.sqrt(3.), -1./np.sqrt(3.)], [+1./np.sqrt(3.), -1./np.sqrt(3.)], [+1./np.sqrt(3.), +1./np.sqrt(3.)], [-1./np.sqrt(3.), +1./np.sqrt(3.)], ]) # integration point weights W = np.array([ [1.], [1.], [1.], [1.], ]) +# number of integration points +nip = 4 + # ================================================================================================== -# assemble stiffness matrix +# B-matrix at each integration point # ================================================================================================== -# allocate matrix -K = np.zeros((ndof, ndof)) +# allocate +B = np.empty((nelem,nip,nne,3,3,3)) +vol = np.empty((nelem,nip)) # loop over nodes -for e in conn: +for e, nodes in enumerate(conn): - # - nodal coordinates - xe = coor[e,:] + # nodal coordinates + xe = coor[nodes,:] - # - allocate element stiffness matrix - Ke = np.zeros((nne*nd, nne*nd)) + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): - # - numerical quadrature - for w, xi in zip(W, Xi): - - # -- shape function gradients (w.r.t. the local element coordinates) + # shape function gradients (w.r.t. the local element coordinates) dNdxi = np.array([ [-.25*(1.-xi[1]), -.25*(1.-xi[0])], [+.25*(1.-xi[1]), -.25*(1.+xi[0])], [+.25*(1.+xi[1]), +.25*(1.+xi[0])], [-.25*(1.+xi[1]), +.25*(1.-xi[0])], ]) - # -- Jacobian - J = np.zeros((nd, nd)) + # Jacobian + Je = np.einsum('mi,mj->ij', dNdxi, xe) + invJe = np.linalg.inv(Je) + detJe = np.linalg.det(Je) + + # shape function gradients (w.r.t. the global coordinates) + dNdxe = np.einsum('ij,mj->mi', invJe, dNdxi) + # compute B-matrix and integration-point volume and store for later use for m in range(nne): - for i in range(nd): - for j in range(nd): - J[i,j] += dNdxi[m,i] * xe[m,j] - Jinv = np.linalg.inv(J) - Jdet = np.linalg.det(J) + Be = np.zeros((3,3,3)) - # -- shape function gradients (w.r.t. the global coordinates) - dNdx = np.zeros((nne,nd)) + Be[0,0,0] = dNdxe[m,0] + Be[0,1,1] = dNdxe[m,0] + Be[1,0,0] = dNdxe[m,1] + Be[1,1,1] = dNdxe[m,1] - for m in range(nne): - for i in range(nd): - for j in range(nd): - dNdx[m,i] += Jinv[i,j] * dNdxi[m,j] + B[e,q,m,:,:,:] = Be - # -- assemble to element stiffness matrix - for m in range(nne): + vol[e,q] = w * detJe * thick + +# ================================================================================================== +# stiffness tensor at each integration point (provides constitutive response and 'tangent') +# ================================================================================================== + +# identity tensors (per integration point) +i = np.eye(3) +I = np.einsum('ij ,...' , i ,np.ones([nelem,nip])) +I4 = np.einsum('ijkl,...->...ijkl',np.einsum('il,jk',i,i),np.ones([nelem,nip])) +I4rt = np.einsum('ijkl,...->...ijkl',np.einsum('ik,jl',i,i),np.ones([nelem,nip])) +I4s = (I4+I4rt)/2. +II = dyad22(I,I) +I4d = I4s-II/3. - Bm = np.zeros((3,3,3)) +# bulk and shear modulus (homogeneous) +K = 0.8333333333333333 * np.ones((nelem, nip)) +G = 0.3846153846153846 * np.ones((nelem, nip)) - Bm[0,0,0] = dNdx[m,0] - Bm[0,1,1] = dNdx[m,0] +# elasticity tensor (per integration point) +C4 = np.einsum('eq,eqijkl->eqijkl', K, II) + 2. * np.einsum('eq,eqijkl->eqijkl', G, I4d) - Bm[1,0,0] = dNdx[m,1] - Bm[1,1,1] = dNdx[m,1] +# ================================================================================================== +# strain from nodal displacements, stress from constitutive response +# ================================================================================================== - for n in range(nne): +# allocate strain tensor per integration point +Eps = np.empty((nelem,nip,3,3)) - Bn = np.zeros((3,3,3)) +# loop over nodes +for e, nodes in enumerate(conn): - Bn[0,0,0] = dNdx[n,0] - Bn[0,1,1] = dNdx[n,0] + # nodal displacements in 3-d + # u_z = 0 + ue = np.zeros((nne,3)) + ue[:,0] = disp[nodes,0] + ue[:,1] = disp[nodes,1] - Bn[1,0,0] = dNdx[n,1] - Bn[1,1,1] = dNdx[n,1] + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): - Kmn = ddot33(transpose3(Bm),ddot43(C4,Bn)) + # alias integration point values + Be = B[e,q,:,:,:,:] - iim = np.array([ m*nd+0, m*nd+1 ]) - iin = np.array([ n*nd+0, n*nd+1 ]) + # displacement gradient + gradu = np.einsum('mijk,mk->ij', Be, ue) - Ke[np.ix_(iim,iin)] += Kmn[np.ix_([0,1], [0,1])] * w * Jdet * thick + # compute strain tensor, and store per integration point + Eps[e,q] = .5 * ( gradu + gradu.T ) - # - assemble to global stiffness matrix - iie = dofs[e,:].ravel() +# constitutive response: strain tensor -> stress tensor (per integration point) +Sig = ddot42(C4, Eps) +# ================================================================================================== +# internal force from stress +# ================================================================================================== + +# allocate internal force +fint = np.zeros((ndof)) + +# loop over nodes +for e, nodes in enumerate(conn): + + # allocate element internal force + fe = np.zeros((nne*ndim)) + + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): + + # alias integration point values + sig = Sig[e,q,:,:] + Be = B [e,q,:,:,:,:] + dV = vol[e,q] + + # assemble to element internal force + # (plane strain: force in z-direction irrelevant) + # Bm = B[e,q,m,:,:,:] + # fm = ddot32(transpose3(Bm), sig) * dV + # fe[[m*ndim+0, m*ndim+1]] = [fm[m,2], fm[m,0]] + fq = np.einsum('mijk,ij->mk', Be, sig) * dV + fe += fq[:,:2].reshape(nne*ndim) + + # assemble to global stiffness matrix + iie = dofs[nodes,:].ravel() + fint[iie] += fe + +# ================================================================================================== +# stiffness matrix from 'tangent' +# ================================================================================================== + +# allocate stiffness matrix +K = np.zeros((ndof, ndof)) + +# loop over nodes +for e, nodes in enumerate(conn): + + # allocate element stiffness matrix + Ke = np.zeros((nne*ndim, nne*ndim)) + + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): + + # alias integration point values + c4 = C4 [e,q,:,:,:,:] + Be = B [e,q,:,:,:,:] + dV = vol[e,q] + + # assemble to element stiffness matrix + # Bm = B[e,q,m,:,:,:] + # Bn = B[e,q,n,:,:,:] + # Kmn = ddot33(transpose3(Bm),ddot43(C4,Bn)) * dV + # Ke[[m*ndim+0,m*ndim+1], [n*ndim+0,n*ndim+1]] += Kmn[[2,0], [2,0]] + Kq = np.einsum('mabc,abde,nedf->mcnf', Be, c4, Be) * dV + Ke += Kq[:,:2,:,:2].reshape(nne*ndim, nne*ndim) + + # assemble to global stiffness matrix + iie = dofs[nodes,:].ravel() K[np.ix_(iie,iie)] += Ke # ================================================================================================== # partition and solve # ================================================================================================== -# zero-initialize displacements and forces -f = np.zeros((ndof)) -u = np.zeros((ndof)) +# prescribed external force: zero on all free DOFs +# (other DOFs ignored in solution, the reaction forces on the DOFs are computed below) +fext = np.zeros((ndof)) + +# DOF-partitioning: ['u'nknown, 'p'rescribed] +# - prescribed +iip = np.hstack(( + dofs[nodesBottom,1], + dofs[nodesLeft ,0], + dofs[nodesRight ,0], +)) +# - unknown +iiu = np.setdiff1d(dofs.ravel(), iip) # fixed displacements -# - zero-initialize -iip = np.empty((0),dtype='int' ) -up = np.empty((0),dtype='float') -# - 'y = 0' : 'u_y = 0' -idof = dofs[inode[0,:], 1] -iip = np.hstack(( iip, idof )) -up = np.hstack(( up , 0.0 * np.ones(idof.shape) )) -# - 'x = 0' : 'u_x = 0' -idof = dofs[inode[:,0], 0] -iip = np.hstack(( iip, idof )) -up = np.hstack(( up , 0.0 * np.ones(idof.shape) )) -# - 'x = 1' : 'u_x = 0.1' -idof = dofs[inode[:,-1], 0] -iip = np.hstack(( iip, idof )) -up = np.hstack(( up , 0.1 * np.ones(idof.shape) )) - -# free displacements -iiu = np.setdiff1d(dofs.ravel(), iip) +up = np.hstack(( + 0.0 * np.ones((len(nodesBottom))), + 0.0 * np.ones((len(nodesLeft ))), + 0.1 * np.ones((len(nodesRight ))), +)) + +# residual force +r = fext - fint # partition # - stiffness matrix -Kuu = K[np.ix_(iiu, iiu)] -Kup = K[np.ix_(iiu, iip)] -Kpu = K[np.ix_(iip, iiu)] -Kpp = K[np.ix_(iip, iip)] -# - external force -fu = f[iiu] +Kuu = K[np.ix_(iiu, iiu)] +Kup = K[np.ix_(iiu, iip)] +Kpu = K[np.ix_(iip, iiu)] +Kpp = K[np.ix_(iip, iip)] +# - residual force +ru = r[iiu] -# solve -uu = np.linalg.solve(Kuu, fu - Kup.dot(up)) -fp = Kpu.dot(uu) + Kpp.dot(up) +# solve for unknown displacement DOFs +uu = np.linalg.solve(Kuu, ru - Kup.dot(up)) # assemble +u = np.empty((ndof)) u[iiu] = uu u[iip] = up -f[iip] = fp -# convert to nodal displacement and forces +# convert to nodal displacements disp = u[dofs] -fext = f[dofs] + +# ================================================================================================== +# strain from nodal displacements, stress from constitutive response +# ================================================================================================== + +# allocate strain tensor per integration point +Eps = np.empty((nelem,nip,3,3)) + +# loop over nodes +for e, nodes in enumerate(conn): + + # nodal displacements in 3-d + # u_z = 0 + ue = np.zeros((nne,3)) + ue[:,0] = disp[nodes,0] + ue[:,1] = disp[nodes,1] + + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): + + # alias integration point values + Be = B[e,q,:,:,:,:] + + # displacement gradient + gradu = np.einsum('mijk,mk->ij', Be, ue) + + # compute strain tensor, and store per integration point + Eps[e,q] = .5 * ( gradu + gradu.T ) + +# constitutive response: strain tensor -> stress tensor (per integration point) +Sig = ddot42(C4, Eps) + +# ================================================================================================== +# internal force from stress, reaction (external) force from internal force on fixed nodes +# ================================================================================================== + +# allocate internal force +fint = np.zeros((ndof)) + +# loop over nodes +for e, nodes in enumerate(conn): + + # allocate element internal force + fe = np.zeros((nne*ndim)) + + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): + + # alias integration point values + sig = Sig[e,q,:,:] + Be = B [e,q,:,:,:,:] + dV = vol[e,q] + + # assemble to element internal force + # (plane strain: force in z-direction irrelevant) + # Bm = B[e,q,m,:,:,:] + # fm = ddot32(transpose3(Bm), sig) * dV + # fe[[m*ndim+0, m*ndim+1]] = [fm[m,2], fm[m,0]] + fq = np.einsum('mijk,ij->mk', Be, sig) * dV + fe += fq[:,:2].reshape(nne*ndim) + + # assemble to global stiffness matrix + iie = dofs[nodes,:].ravel() + fint[iie] += fe + +# reaction force +fext[iip] = fint[iip] # ================================================================================================== # plot # ================================================================================================== import matplotlib.pyplot as plt -fig, axes = plt.subplots(ncols=2, figsize=(16,8)) +fig, axes = plt.subplots(ncols=2, figsize=(16,8)) + +# reconstruct external force as nodal vectors +fext = fext[dofs] +# plot original nodal positions + displacement field as arrows axes[0].scatter(coor[:,0], coor[:,1]) axes[0].quiver (coor[:,0], coor[:,1], disp[:,0], disp[:,1]) +# plot new nodal positions + external (reaction) force field as arrows axes[1].scatter((coor+disp)[:,0], (coor+disp)[:,1]) axes[1].quiver ((coor+disp)[:,0], (coor+disp)[:,1], fext[:,0], fext[:,1], scale=.1) +# fix axes limits for axis in axes: axis.set_xlim([-0.4, 1.5]) axis.set_ylim([-0.4, 1.5]) plt.show() diff --git a/docs/theory/examples/cartesian_linear-elasticity_periodic-boundary-conditions.py b/docs/theory/examples/cartesian_linear-elasticity_periodic-boundary-conditions.py new file mode 100644 index 0000000..82ed560 --- /dev/null +++ b/docs/theory/examples/cartesian_linear-elasticity_periodic-boundary-conditions.py @@ -0,0 +1,457 @@ +# ================================================================================================== +# +# (c - GPLv3) T.W.J. de Geus (Tom) | tom@geus.me | www.geus.me | github.com/tdegeus/GooseFEM +# +# ================================================================================================== + +import numpy as np + +np.set_printoptions(linewidth=200) + +# ================================================================================================== +# tensor products +# ================================================================================================== + +ddot22 = lambda A2,B2: np.einsum('...ij ,...ji->... ',A2,B2) +ddot42 = lambda A4,B2: np.einsum('...ijkl,...lk->...ij ',A4,B2) +dyad22 = lambda A2,B2: np.einsum('...ij ,...kl->...ijkl',A2,B2) + +# ================================================================================================== +# mesh definition +# ================================================================================================== + +# number of elements +nx = 20 +ny = 20 + +# mesh dimensions +nelem = nx * ny # number of elements +nnode = (nx+1) * (ny+1) # number of nodes +nne = 4 # number of nodes per element +ndim = 2 # number of dimensions +ndof = nnode * ndim # total number of degrees of freedom + +# out-of-plane thickness +thick = 1. + +# zero-initialise coordinates, displacements, and connectivity +coor = np.zeros((nnode,ndim), dtype='float') +disp = np.zeros((nnode,ndim), dtype='float') +conn = np.zeros((nelem,nne ), dtype='int' ) + +# coordinates +# - grid of points +x,y = np.meshgrid(np.linspace(0,1,nx+1), np.linspace(0,1,ny+1)) +# - store from grid of points +coor[:,0] = x.ravel() +coor[:,1] = y.ravel() + +# connectivity +# - grid of node numbers +inode = np.arange(nnode).reshape(ny+1, nx+1) +# - store from grid of node numbers +conn[:,0] = inode[ :-1, :-1].ravel() +conn[:,1] = inode[ :-1,1: ].ravel() +conn[:,2] = inode[1: ,1: ].ravel() +conn[:,3] = inode[1: , :-1].ravel() +# - node sets +nodesLeft = inode[ :, 0] +nodesRight = inode[ :,-1] +nodesBottom = inode[ 0, :] +nodesTop = inode[-1, :] + +# DOF-numbers per node +dofs = np.arange(nnode*ndim).reshape(nnode,ndim) + +# ================================================================================================== +# periodicity +# ================================================================================================== + +# add virtual nodes +# - node set +nodesVirtual = nnode + np.arange(2) +# - update size +nnode += 2 +ndof += 4 +# - add coordinates (position is completely arbitrary) +coor = np.vstack(( coor, np.array([[0.0, 1.1], [0.1, 1.1]]) )) +# - add DOF numbers +dofs = np.vstack(( dofs, np.max(dofs) + 1 + np.arange(4).reshape(2,2) )) + +# tyings (dependent, independent) +tyings = np.vstack(( + np.hstack(( nodesRight[1:-1].reshape(-1,1) , nodesLeft [1:-1].reshape(-1,1) )), + np.hstack(( nodesTop [1:-1].reshape(-1,1) , nodesBottom[1:-1].reshape(-1,1) )), + np.hstack(( nodesRight[ 0].reshape(-1,1) , nodesBottom[ 0].reshape(-1,1) )), + np.hstack(( nodesRight[ -1].reshape(-1,1) , nodesBottom[ 0].reshape(-1,1) )), + np.hstack(( nodesLeft [ -1].reshape(-1,1) , nodesBottom[ 0].reshape(-1,1) )), +)) + +# DOF-sets +# - dependent DOFs +iid = dofs[tyings[:,0],:].ravel() +# - prescribed DOfs +iip = np.hstack(( + dofs[nodesBottom[0],:].ravel(), + dofs[nodesVirtual ,:].ravel(), +)) +# - independent DOFs +iii = np.setdiff1d(dofs.ravel(), iid) +# - unknown DOFs +iiu = np.setdiff1d(iii, iip) + +# renumber +# - list +renum = np.zeros(dofs.size, dtype='int') +renum[iiu] = np.arange(iiu.size) +renum[iip] = np.arange(iip.size) + iiu.size +renum[iid] = np.arange(iid.size) + iiu.size + iip.size +# - apply +iiu = renum[iiu ] +iip = renum[iip ] +dofs = renum[dofs] +# - define auxiliary DOF sets +iii = np.hstack((iiu, iip)) +iid = np.arange(iid.size) + iii.size + +# nodal tyings +# - zero-initialize +Cdi = np.zeros((iid.size, iii.size)) +# - tie +iie = 2 * np.arange(tyings.shape[0]) +Cdi[iie ,dofs[tyings[:,1],0]] = 1. +Cdi[iie+1,dofs[tyings[:,1],1]] = 1. +Cdi[iie ,dofs[nodesVirtual[0],0]] = coor[tyings[:,0],0] - coor[tyings[:,1],0] # (F-I)_xx +Cdi[iie ,dofs[nodesVirtual[0],1]] = coor[tyings[:,0],1] - coor[tyings[:,1],1] # (F-I)_xy +Cdi[iie+1,dofs[nodesVirtual[1],0]] = coor[tyings[:,0],0] - coor[tyings[:,1],0] # (F-I)_yx +Cdi[iie+1,dofs[nodesVirtual[1],1]] = coor[tyings[:,0],1] - coor[tyings[:,1],1] # (F-I)_yy + +# ================================================================================================== +# quadrature definition +# ================================================================================================== + +# integration point coordinates (local element coordinates) +Xi = np.array([ + [-1./np.sqrt(3.), -1./np.sqrt(3.)], + [+1./np.sqrt(3.), -1./np.sqrt(3.)], + [+1./np.sqrt(3.), +1./np.sqrt(3.)], + [-1./np.sqrt(3.), +1./np.sqrt(3.)], +]) + +# integration point weights +W = np.array([ + [1.], + [1.], + [1.], + [1.], +]) + +# number of integration points +nip = 4 + +# ================================================================================================== +# gradient of the shape functions at each integration point +# ================================================================================================== + +# allocate +dNx = np.empty((nelem,nip,nne,ndim)) +vol = np.empty((nelem,nip)) + +# loop over nodes +for e, nodes in enumerate(conn): + + # nodal coordinates + xe = coor[nodes,:] + + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): + + # shape function gradients (w.r.t. the local element coordinates) + dNdxi = np.array([ + [-.25*(1.-xi[1]), -.25*(1.-xi[0])], + [+.25*(1.-xi[1]), -.25*(1.+xi[0])], + [+.25*(1.+xi[1]), +.25*(1.+xi[0])], + [-.25*(1.+xi[1]), +.25*(1.-xi[0])], + ]) + + # Jacobian + Je = np.einsum('mi,mj->ij', dNdxi, xe) + invJe = np.linalg.inv(Je) + detJe = np.linalg.det(Je) + + # shape function gradients (w.r.t. the global coordinates) + dNdxe = np.einsum('ij,mj->mi', invJe, dNdxi) + + # store for later use + dNx[e,q,:,:] = dNdxe + vol[e,q] = w * detJe * thick + +# ================================================================================================== +# stiffness tensor at each integration point (provides constitutive response and 'tangent') +# ================================================================================================== + +# identity tensors (per integration point) +i = np.eye(3) +I = np.einsum('ij ,...' , i ,np.ones([nelem,nip])) +I4 = np.einsum('ijkl,...->...ijkl',np.einsum('il,jk',i,i),np.ones([nelem,nip])) +I4rt = np.einsum('ijkl,...->...ijkl',np.einsum('ik,jl',i,i),np.ones([nelem,nip])) +I4s = (I4+I4rt)/2. +II = dyad22(I,I) +I4d = I4s-II/3. + +# bulk and shear modulus (homogeneous) +K = 0.8333333333333333 * np.ones((nelem, nip)) +G = 0.3846153846153846 * np.ones((nelem, nip)) + +# elasticity tensor (per integration point) +C4 = np.einsum('eq,eqijkl->eqijkl', K, II) + 2. * np.einsum('eq,eqijkl->eqijkl', G, I4d) + +# ================================================================================================== +# strain from nodal displacements, stress from constitutive response +# ================================================================================================== + +# zero-initialise strain tensor per integration point +# (plain strain -> all z-components are not written and should remain zero at all times) +Eps = np.zeros((nelem,nip,3,3)) + +# loop over nodes +for e, nodes in enumerate(conn): + + # nodal displacements + ue = disp[nodes,:] + + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): + + # alias integration point values + dNdxe = dNx[e,q,:,:] + + # displacement gradient + gradu = np.einsum('mi,mj->ij', dNdxe, ue) + + # compute strain tensor, and store per integration point + # (use plain strain to convert 2-d to 3-d tensor) + Eps[e,q,:2,:2] = .5 * ( gradu + gradu.T ) + +# constitutive response: strain tensor -> stress tensor (per integration point) +Sig = ddot42(C4, Eps) + +# ================================================================================================== +# internal force from stress +# ================================================================================================== + +# allocate internal force +fint = np.zeros((ndof)) + +# loop over nodes +for e, nodes in enumerate(conn): + + # allocate element internal force + fe = np.zeros((nne*ndim)) + + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): + + # alias integration point values + # (plane strain: stress in z-direction irrelevant) + sig = Sig[e,q,:2,:2] + dNdxe = dNx[e,q,:,:] + dV = vol[e,q] + + # assemble to element internal force + # fe[m*nd+j] += dNdx[m,i] * sig[i,j] * dV + fe += ( np.einsum('mi,ij->mj', dNdxe, sig) * dV ).reshape(nne*ndim) + + # assemble to global stiffness matrix + iie = dofs[nodes,:].ravel() + fint[iie] += fe + +# ================================================================================================== +# stiffness matrix from 'tangent' +# ================================================================================================== + +# allocate stiffness matrix +K = np.zeros((ndof, ndof)) + +# loop over nodes +for e, nodes in enumerate(conn): + + # allocate element stiffness matrix + Ke = np.zeros((nne*ndim, nne*ndim)) + + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): + + # alias integration point values + c4 = C4 [e,q,:2,:2,:2,:2] + dNdxe = dNx[e,q,:,:] + dV = vol[e,q] + + # assemble to element stiffness matrix + # Ke[m*nd+j, n*nd+k] += dNdx[m,i] * C4[i,j,k,l] * dNdx[n,l] * dV + Ke += ( np.einsum('mi,ijkl,nl->mjnk', dNdxe, c4, dNdxe) * dV ).reshape(nne*ndim, nne*ndim) + + # assemble to global stiffness matrix + iie = dofs[nodes,:].ravel() + K[np.ix_(iie,iie)] += Ke + +# ================================================================================================== +# partition and solve +# ================================================================================================== + +# prescribed external force: zero on all free DOFs +# (other DOFs ignored in solution, the reaction forces on the DOFs are computed below) +fext = np.zeros((ndof)) + +# fixed displacements +up = np.array([ + 0.0, # suppress rigid-body motion in x + 0.0, # suppress rigid-body motion in x + 0.0, # (F-I)_xx + 0.1, # (F-I)_xy + 0.0, # (F-I)_yx + 0.0, # (F-I)_yy +]) + +# residual force +r = fext - fint + +# partition in independent and dependent part +# - stiffness matrix +Kii = K[np.ix_(iii, iii)] +Kid = K[np.ix_(iii, iid)] +Kdi = K[np.ix_(iid, iii)] +Kdd = K[np.ix_(iid, iid)] +# - residual force +ri = r[iii] +rd = r[iid] + +# condense system +Kii = Kii + np.dot(Kid, Cdi) + np.dot(Cdi.T, Kdi) + np.dot(Cdi.T, np.dot(Kdd, Cdi)) +ri = ri + np.dot(Cdi.T, rd) + +# partition +# - stiffness matrix +Kuu = Kii[np.ix_(iiu, iiu)] +Kup = Kii[np.ix_(iiu, iip)] +Kpu = Kii[np.ix_(iip, iiu)] +Kpp = Kii[np.ix_(iip, iip)] +# - residual force +ru = ri[iiu] + +# solve for unknown displacement DOFs +uu = np.linalg.solve(Kuu, ru - Kup.dot(up)) + +# assemble +ui = np.empty((iii.size)) +ui[iiu] = uu +ui[iip] = up + +# reconstruct +# - dependent displacement DOFs +ud = np.dot(Cdi, ui) +# - assemble +u = np.empty((ndof)) +u[iii] = ui +u[iid] = ud + +# convert to nodal displacements +disp = u[dofs] + +# ================================================================================================== +# strain from nodal displacements, stress from constitutive response +# ================================================================================================== + +# zero-initialise strain tensor per integration point +# (plain strain -> all z-components are not written and should remain zero at all times) +Eps = np.zeros((nelem,nip,3,3)) + +# loop over nodes +for e, nodes in enumerate(conn): + + # nodal displacements + ue = disp[nodes,:] + + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): + + # alias integration point values + dNdxe = dNx[e,q,:,:] + + # displacement gradient + gradu = np.einsum('mi,mj->ij', dNdxe, ue) + + # compute strain tensor, and store per integration point + # (use plain strain to convert 2-d to 3-d tensor) + Eps[e,q,:2,:2] = .5 * ( gradu + gradu.T ) + +# constitutive response: strain tensor -> stress tensor (per integration point) +Sig = ddot42(C4, Eps) + +# ================================================================================================== +# internal force from stress, reaction (external) force from internal force on fixed nodes +# ================================================================================================== + +# allocate internal force +fint = np.zeros((ndof)) + +# loop over nodes +for e, nodes in enumerate(conn): + + # allocate element internal force + fe = np.zeros((nne*ndim)) + + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): + + # alias integration point values + # (plane strain: stress in z-direction irrelevant) + sig = Sig[e,q,:2,:2] + dNdxe = dNx[e,q,:,:] + dV = vol[e,q] + + # assemble to element internal force + # fe[m*nd+j] += dNdx[m,i] * sig[i,j] * dV + fe += ( np.einsum('mi,ij->mj', dNdxe, sig) * dV ).reshape(nne*ndim) + + # assemble to global stiffness matrix + iie = dofs[nodes,:].ravel() + fint[iie] += fe + +# residual force +# - all DOFs +r = fext - fint +# - partition [independent, dependent] +ri = r[iii] +rd = r[iid] +# - condense system: apply tying relations +ri = ri + np.dot(Cdi.T, rd) + +# reaction force +fext[iip] = -ri[iip] + +# ================================================================================================== +# plot +# ================================================================================================== + +import matplotlib.pyplot as plt + +fig, axes = plt.subplots(ncols=2, figsize=(16,8)) + +# reconstruct external force as nodal vectors (for all nodes) +fext = fext[dofs] + +# plot original nodal positions + displacement field as arrows +axes[0].scatter(coor[:,0], coor[:,1]) +axes[0].quiver (coor[:,0], coor[:,1], disp[:,0], disp[:,1]) + +# plot new nodal positions + external (reaction) force field as arrows +axes[1].scatter((coor+disp)[:,0], (coor+disp)[:,1]) +axes[1].quiver ((coor+disp)[:,0], (coor+disp)[:,1], fext[:,0], fext[:,1], scale=.1) + +# fix axes limits +for axis in axes: + axis.set_xlim([-0.4, 1.5]) + axis.set_ylim([-0.4, 1.5]) + +plt.show() diff --git a/docs/theory/examples/cartesian_linear-elasticity_sparse.py b/docs/theory/examples/cartesian_linear-elasticity_sparse.py index 221df5e..670455e 100644 --- a/docs/theory/examples/cartesian_linear-elasticity_sparse.py +++ b/docs/theory/examples/cartesian_linear-elasticity_sparse.py @@ -1,241 +1,372 @@ +# ================================================================================================== +# +# (c - GPLv3) T.W.J. de Geus (Tom) | tom@geus.me | www.geus.me | github.com/tdegeus/GooseFEM +# +# ================================================================================================== import numpy as np from scipy.sparse import dok_matrix from scipy.sparse.linalg import spsolve np.set_printoptions(linewidth=200) # ================================================================================================== -# identity tensors -# ================================================================================================== - -II = np.zeros((3,3,3,3)) -I4 = np.zeros((3,3,3,3)) -I4rt = np.zeros((3,3,3,3)) -Is = np.zeros((3,3,3,3)) - -for i in range(3): - for j in range(3): - for k in range(3): - for l in range(3): - if ( i == j and k == l ): - II[i,j,k,l] = 1. - -for i in range(3): - for j in range(3): - for k in range(3): - for l in range(3): - if ( i == l and j == k ): - I4[i,j,k,l] = 1. - -for i in range(3): - for j in range(3): - for k in range(3): - for l in range(3): - if ( i == k and j == l ): - I4rt[i,j,k,l] = 1. - -I4s = ( I4 + I4rt ) / 2. -I4d = ( I4s - II/3. ) - -# ================================================================================================== -# elasticity tensor +# tensor products # ================================================================================================== -# bulk and shear modulus -K = 0.8333333333333333 -G = 0.3846153846153846 - -# elasticity tensor (3d) -C4 = K * II + 2. * G * I4d +ddot22 = lambda A2,B2: np.einsum('...ij ,...ji->... ',A2,B2) +ddot42 = lambda A4,B2: np.einsum('...ijkl,...lk->...ij ',A4,B2) +dyad22 = lambda A2,B2: np.einsum('...ij ,...kl->...ijkl',A2,B2) # ================================================================================================== # mesh definition # ================================================================================================== # number of elements nx = 20 ny = 20 # mesh dimensions nelem = nx * ny # number of elements nnode = (nx+1) * (ny+1) # number of nodes nne = 4 # number of nodes per element -nd = 2 # number of dimensions -ndof = nnode * nd # total number of degrees of freedom +ndim = 2 # number of dimensions +ndof = nnode * ndim # total number of degrees of freedom # out-of-plane thickness thick = 1. -# coordinates and connectivity: zero-initialize -coor = np.zeros((nnode,nd ), dtype='float') -conn = np.zeros((nelem,nne), dtype='int' ) +# zero-initialise coordinates, displacements, and connectivity +coor = np.zeros((nnode,ndim), dtype='float') +disp = np.zeros((nnode,ndim), dtype='float') +conn = np.zeros((nelem,nne ), dtype='int' ) -# coordinates: set +# coordinates # - grid of points x,y = np.meshgrid(np.linspace(0,1,nx+1), np.linspace(0,1,ny+1)) # - store from grid of points coor[:,0] = x.ravel() coor[:,1] = y.ravel() -# connectivity: set +# connectivity # - grid of node numbers inode = np.arange(nnode).reshape(ny+1, nx+1) # - store from grid of node numbers conn[:,0] = inode[ :-1, :-1].ravel() conn[:,1] = inode[ :-1,1: ].ravel() conn[:,2] = inode[1: ,1: ].ravel() conn[:,3] = inode[1: , :-1].ravel() +# - node sets +nodesLeft = inode[ :, 0] +nodesRight = inode[ :,-1] +nodesBottom = inode[ 0, :] +nodesTop = inode[-1, :] # DOF-numbers per node -dofs = np.arange(nnode*nd).reshape(nnode,nd) +dofs = np.arange(nnode*ndim).reshape(nnode,ndim) # ================================================================================================== # quadrature definition # ================================================================================================== # integration point coordinates (local element coordinates) Xi = np.array([ [-1./np.sqrt(3.), -1./np.sqrt(3.)], [+1./np.sqrt(3.), -1./np.sqrt(3.)], [+1./np.sqrt(3.), +1./np.sqrt(3.)], [-1./np.sqrt(3.), +1./np.sqrt(3.)], ]) # integration point weights W = np.array([ [1.], [1.], [1.], [1.], ]) +# number of integration points +nip = 4 + # ================================================================================================== -# assemble stiffness matrix +# gradient of the shape functions at each integration point # ================================================================================================== -# allocate matrix -K = dok_matrix((ndof, ndof), dtype=np.float) +# allocate +dNx = np.empty((nelem,nip,nne,ndim)) +vol = np.empty((nelem,nip)) # loop over nodes -for e in conn: +for e, nodes in enumerate(conn): - # - nodal coordinates - xe = coor[e,:] + # nodal coordinates + xe = coor[nodes,:] - # - allocate element stiffness matrix - Ke = np.zeros((nne*nd, nne*nd)) + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): - # - numerical quadrature - for w, xi in zip(W, Xi): - - # -- shape function gradients (w.r.t. the local element coordinates) + # shape function gradients (w.r.t. the local element coordinates) dNdxi = np.array([ [-.25*(1.-xi[1]), -.25*(1.-xi[0])], [+.25*(1.-xi[1]), -.25*(1.+xi[0])], [+.25*(1.+xi[1]), +.25*(1.+xi[0])], [-.25*(1.+xi[1]), +.25*(1.-xi[0])], ]) - # -- Jacobian - J = np.zeros((nd, nd)) + # Jacobian + Je = np.einsum('mi,mj->ij', dNdxi, xe) + invJe = np.linalg.inv(Je) + detJe = np.linalg.det(Je) + + # shape function gradients (w.r.t. the global coordinates) + dNdxe = np.einsum('ij,mj->mi', invJe, dNdxi) + + # store for later use + dNx[e,q,:,:] = dNdxe + vol[e,q] = w * detJe * thick + +# ================================================================================================== +# stiffness tensor at each integration point (provides constitutive response and 'tangent') +# ================================================================================================== + +# identity tensors (per integration point) +i = np.eye(3) +I = np.einsum('ij ,...' , i ,np.ones([nelem,nip])) +I4 = np.einsum('ijkl,...->...ijkl',np.einsum('il,jk',i,i),np.ones([nelem,nip])) +I4rt = np.einsum('ijkl,...->...ijkl',np.einsum('ik,jl',i,i),np.ones([nelem,nip])) +I4s = (I4+I4rt)/2. +II = dyad22(I,I) +I4d = I4s-II/3. - for m in range(nne): - for i in range(nd): - for j in range(nd): - J[i,j] += dNdxi[m,i] * xe[m,j] +# bulk and shear modulus (homogeneous) +K = 0.8333333333333333 * np.ones((nelem, nip)) +G = 0.3846153846153846 * np.ones((nelem, nip)) + +# elasticity tensor (per integration point) +C4 = np.einsum('eq,eqijkl->eqijkl', K, II) + 2. * np.einsum('eq,eqijkl->eqijkl', G, I4d) + +# ================================================================================================== +# strain from nodal displacements, stress from constitutive response +# ================================================================================================== + +# zero-initialise strain tensor per integration point +# (plain strain -> all z-components are not written and should remain zero at all times) +Eps = np.zeros((nelem,nip,3,3)) + +# loop over nodes +for e, nodes in enumerate(conn): + + # nodal displacements + ue = disp[nodes,:] + + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): + + # alias integration point values + dNdxe = dNx[e,q,:,:] + + # displacement gradient + gradu = np.einsum('mi,mj->ij', dNdxe, ue) + + # compute strain tensor, and store per integration point + # (use plain strain to convert 2-d to 3-d tensor) + Eps[e,q,:2,:2] = .5 * ( gradu + gradu.T ) + +# constitutive response: strain tensor -> stress tensor (per integration point) +Sig = ddot42(C4, Eps) + +# ================================================================================================== +# internal force from stress +# ================================================================================================== + +# allocate internal force +fint = np.zeros((ndof)) + +# loop over nodes +for e, nodes in enumerate(conn): - Jinv = np.linalg.inv(J) - Jdet = np.linalg.det(J) + # allocate element internal force + fe = np.zeros((nne*ndim)) - # -- shape function gradients (w.r.t. the global coordinates) - dNdx = np.zeros((nne,nd)) + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): - for m in range(nne): - for i in range(nd): - for j in range(nd): - dNdx[m,i] += Jinv[i,j] * dNdxi[m,j] + # alias integration point values + # (plane strain: stress in z-direction irrelevant) + sig = Sig[e,q,:2,:2] + dNdxe = dNx[e,q,:,:] + dV = vol[e,q] - # -- assemble to element stiffness matrix - for m in range(nne): - for n in range(nne): - for i in range(nd): - for j in range(nd): - for k in range(nd): - for l in range(nd): - Ke[m*nd+j, n*nd+k] += dNdx[m,i] * C4[i,j,k,l] * dNdx[n,l] * w * Jdet * thick + # assemble to element internal force + # fe[m*nd+j] += dNdx[m,i] * sig[i,j] * dV + fe += ( np.einsum('mi,ij->mj', dNdxe, sig) * dV ).reshape(nne*ndim) - # - assemble to global stiffness matrix - iie = dofs[e,:].ravel() + # assemble to global stiffness matrix + iie = dofs[nodes,:].ravel() + fint[iie] += fe +# ================================================================================================== +# stiffness matrix from 'tangent' +# ================================================================================================== + +# allocate stiffness matrix +K = dok_matrix((ndof, ndof), dtype=np.float) + +# loop over nodes +for e, nodes in enumerate(conn): + + # allocate element stiffness matrix + Ke = np.zeros((nne*ndim, nne*ndim)) + + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): + + # alias integration point values + c4 = C4 [e,q,:2,:2,:2,:2] + dNdxe = dNx[e,q,:,:] + dV = vol[e,q] + + # assemble to element stiffness matrix + # Ke[m*nd+j, n*nd+k] += dNdx[m,i] * C4[i,j,k,l] * dNdx[n,l] * dV + Ke += ( np.einsum('mi,ijkl,nl->mjnk', dNdxe, c4, dNdxe) * dV ).reshape(nne*ndim, nne*ndim) + + # assemble to global stiffness matrix + iie = dofs[nodes,:].ravel() K[np.ix_(iie,iie)] += Ke # ================================================================================================== # partition and solve # ================================================================================================== -# zero-initialize displacements and forces -f = np.zeros((ndof)) -u = np.zeros((ndof)) +# prescribed external force: zero on all free DOFs +# (other DOFs ignored in solution, the reaction forces on the DOFs are computed below) +fext = np.zeros((ndof)) + +# DOF-partitioning: ['u'nknown, 'p'rescribed] +# - prescribed +iip = np.hstack(( + dofs[nodesBottom,1], + dofs[nodesLeft ,0], + dofs[nodesRight ,0], +)) +# - unknown +iiu = np.setdiff1d(dofs.ravel(), iip) # fixed displacements -# - zero-initialize -iip = np.empty((0),dtype='int' ) -up = np.empty((0),dtype='float') -# - 'y = 0' : 'u_y = 0' -idof = dofs[inode[0,:], 1] -iip = np.hstack(( iip, idof )) -up = np.hstack(( up , 0.0 * np.ones(idof.shape) )) -# - 'x = 0' : 'u_x = 0' -idof = dofs[inode[:,0], 0] -iip = np.hstack(( iip, idof )) -up = np.hstack(( up , 0.0 * np.ones(idof.shape) )) -# - 'x = 1' : 'u_x = 0.1' -idof = dofs[inode[:,-1], 0] -iip = np.hstack(( iip, idof )) -up = np.hstack(( up , 0.1 * np.ones(idof.shape) )) - -# free displacements -iiu = np.setdiff1d(dofs.ravel(), iip) +up = np.hstack(( + 0.0 * np.ones((len(nodesBottom))), + 0.0 * np.ones((len(nodesLeft ))), + 0.1 * np.ones((len(nodesRight ))), +)) + +# residual force +r = fext - fint # partition # - stiffness matrix Kuu = K.tocsr()[iiu,:].tocsc()[:,iiu] Kup = K.tocsr()[iiu,:].tocsc()[:,iip] Kpu = K.tocsr()[iip,:].tocsc()[:,iiu] Kpp = K.tocsr()[iip,:].tocsc()[:,iip] -# - external force -fu = f[iiu] +# - residual force +ru = r[iiu] -# solve -uu = spsolve(Kuu, fu - Kup.dot(up)) -fp = Kpu.dot(uu) + Kpp.dot(up) +# solve for unknown displacement DOFs +uu = spsolve(Kuu, ru - Kup.dot(up)) # assemble +u = np.empty((ndof)) u[iiu] = uu u[iip] = up -f[iip] = fp -# convert to nodal displacement and forces +# convert to nodal displacements disp = u[dofs] -fext = f[dofs] + +# ================================================================================================== +# strain from nodal displacements, stress from constitutive response +# ================================================================================================== + +# zero-initialise strain tensor per integration point +# (plain strain -> all z-components are not written and should remain zero at all times) +Eps = np.zeros((nelem,nip,3,3)) + +# loop over nodes +for e, nodes in enumerate(conn): + + # nodal displacements + ue = disp[nodes,:] + + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): + + # alias integration point values + dNdxe = dNx[e,q,:,:] + + # displacement gradient + gradu = np.einsum('mi,mj->ij', dNdxe, ue) + + # compute strain tensor, and store per integration point + # (use plain strain to convert 2-d to 3-d tensor) + Eps[e,q,:2,:2] = .5 * ( gradu + gradu.T ) + +# constitutive response: strain tensor -> stress tensor (per integration point) +Sig = ddot42(C4, Eps) + +# ================================================================================================== +# internal force from stress, reaction (external) force from internal force on fixed nodes +# ================================================================================================== + +# allocate internal force +fint = np.zeros((ndof)) + +# loop over nodes +for e, nodes in enumerate(conn): + + # allocate element internal force + fe = np.zeros((nne*ndim)) + + # loop over integration points + for q, (w, xi) in enumerate(zip(W, Xi)): + + # alias integration point values + # (plane strain: stress in z-direction irrelevant) + sig = Sig[e,q,:2,:2] + dNdxe = dNx[e,q,:,:] + dV = vol[e,q] + + # assemble to element internal force + # fe[m*nd+j] += dNdx[m,i] * sig[i,j] * dV + fe += ( np.einsum('mi,ij->mj', dNdxe, sig) * dV ).reshape(nne*ndim) + + # assemble to global stiffness matrix + iie = dofs[nodes,:].ravel() + fint[iie] += fe + +# reaction force +fext[iip] = fint[iip] # ================================================================================================== # plot # ================================================================================================== import matplotlib.pyplot as plt fig, axes = plt.subplots(ncols=2, figsize=(16,8)) +# reconstruct external force as nodal vectors +fext = fext[dofs] + +# plot original nodal positions + displacement field as arrows axes[0].scatter(coor[:,0], coor[:,1]) axes[0].quiver (coor[:,0], coor[:,1], disp[:,0], disp[:,1]) +# plot new nodal positions + external (reaction) force field as arrows axes[1].scatter((coor+disp)[:,0], (coor+disp)[:,1]) axes[1].quiver ((coor+disp)[:,0], (coor+disp)[:,1], fext[:,0], fext[:,1], scale=.1) +# fix axes limits for axis in axes: axis.set_xlim([-0.4, 1.5]) axis.set_ylim([-0.4, 1.5]) plt.show() diff --git a/docs/theory/examples/cartesian_linear-elasticity_sparse_strain.py b/docs/theory/examples/cartesian_linear-elasticity_sparse_strain.py deleted file mode 100644 index 5827bc9..0000000 --- a/docs/theory/examples/cartesian_linear-elasticity_sparse_strain.py +++ /dev/null @@ -1,302 +0,0 @@ - -import numpy as np -from scipy.sparse import dok_matrix -from scipy.sparse.linalg import spsolve - -np.set_printoptions(linewidth=200) - -# ================================================================================================== -# identity tensors -# ================================================================================================== - -II = np.zeros((3,3,3,3)) -I4 = np.zeros((3,3,3,3)) -I4rt = np.zeros((3,3,3,3)) -Is = np.zeros((3,3,3,3)) - -for i in range(3): - for j in range(3): - for k in range(3): - for l in range(3): - if ( i == j and k == l ): - II[i,j,k,l] = 1. - -for i in range(3): - for j in range(3): - for k in range(3): - for l in range(3): - if ( i == l and j == k ): - I4[i,j,k,l] = 1. - -for i in range(3): - for j in range(3): - for k in range(3): - for l in range(3): - if ( i == k and j == l ): - I4rt[i,j,k,l] = 1. - -I4s = ( I4 + I4rt ) / 2. -I4d = ( I4s - II/3. ) - -# ================================================================================================== -# elasticity tensor -# ================================================================================================== - -# bulk and shear modulus -K = 0.8333333333333333 -G = 0.3846153846153846 - -# elasticity tensor (3d) -C4 = K * II + 2. * G * I4d - -# ================================================================================================== -# mesh definition -# ================================================================================================== - -# number of elements -nx = 20 -ny = 20 - -# mesh dimensions -nelem = nx * ny # number of elements -nnode = (nx+1) * (ny+1) # number of nodes -nne = 4 # number of nodes per element -nd = 2 # number of dimensions -nip = 4 # number of integration points -ndof = nnode * nd # total number of degrees of freedom - -# out-of-plane thickness -thick = 1. - -# coordinates and connectivity: zero-initialize -coor = np.zeros((nnode,nd ), dtype='float') -conn = np.zeros((nelem,nne), dtype='int' ) - -# coordinates: set -# - grid of points -x,y = np.meshgrid(np.linspace(0,1,nx+1), np.linspace(0,1,ny+1)) -# - store from grid of points -coor[:,0] = x.ravel() -coor[:,1] = y.ravel() - -# connectivity: set -# - grid of node numbers -inode = np.arange(nnode).reshape(ny+1, nx+1) -# - store from grid of node numbers -conn[:,0] = inode[ :-1, :-1].ravel() -conn[:,1] = inode[ :-1,1: ].ravel() -conn[:,2] = inode[1: ,1: ].ravel() -conn[:,3] = inode[1: , :-1].ravel() - -# DOF-numbers per node -dofs = np.arange(nnode*nd).reshape(nnode,nd) - -# ================================================================================================== -# quadrature definition -# ================================================================================================== - -# integration point coordinates (local element coordinates) -Xi = np.array([ - [-1./np.sqrt(3.), -1./np.sqrt(3.)], - [+1./np.sqrt(3.), -1./np.sqrt(3.)], - [+1./np.sqrt(3.), +1./np.sqrt(3.)], - [-1./np.sqrt(3.), +1./np.sqrt(3.)], -]) - -# integration point weights -W = np.array([ - [1.], - [1.], - [1.], - [1.], -]) - -# ================================================================================================== -# assemble stiffness matrix -# ================================================================================================== - -# allocate matrix -K = dok_matrix((ndof, ndof), dtype=np.float) - -# loop over nodes -for e in conn: - - # - nodal coordinates - xe = coor[e,:] - - # - allocate element stiffness matrix - Ke = np.zeros((nne*nd, nne*nd)) - - # - numerical quadrature - for w, xi in zip(W, Xi): - - # -- shape function gradients (w.r.t. the local element coordinates) - dNdxi = np.array([ - [-.25*(1.-xi[1]), -.25*(1.-xi[0])], - [+.25*(1.-xi[1]), -.25*(1.+xi[0])], - [+.25*(1.+xi[1]), +.25*(1.+xi[0])], - [-.25*(1.+xi[1]), +.25*(1.-xi[0])], - ]) - - # -- Jacobian - J = np.zeros((nd, nd)) - - for m in range(nne): - for i in range(nd): - for j in range(nd): - J[i,j] += dNdxi[m,i] * xe[m,j] - - Jinv = np.linalg.inv(J) - Jdet = np.linalg.det(J) - - # -- shape function gradients (w.r.t. the global coordinates) - dNdx = np.zeros((nne,nd)) - - for m in range(nne): - for i in range(nd): - for j in range(nd): - dNdx[m,i] += Jinv[i,j] * dNdxi[m,j] - - # -- assemble to element stiffness matrix - for m in range(nne): - for n in range(nne): - for i in range(nd): - for j in range(nd): - for k in range(nd): - for l in range(nd): - Ke[m*nd+j, n*nd+k] += dNdx[m,i] * C4[i,j,k,l] * dNdx[n,l] * w * Jdet * thick - - # - assemble to global stiffness matrix - iie = dofs[e,:].ravel() - - K[np.ix_(iie,iie)] += Ke - -# ================================================================================================== -# partition and solve -# ================================================================================================== - -# zero-initialize displacements and forces -f = np.zeros((ndof)) -u = np.zeros((ndof)) - -# fixed displacements -# - zero-initialize -iip = np.empty((0),dtype='int' ) -up = np.empty((0),dtype='float') -# - 'y = 0' : 'u_y = 0' -idof = dofs[inode[0,:], 1] -iip = np.hstack(( iip, idof )) -up = np.hstack(( up , 0.0 * np.ones(idof.shape) )) -# - 'x = 0' : 'u_x = 0' -idof = dofs[inode[:,0], 0] -iip = np.hstack(( iip, idof )) -up = np.hstack(( up , 0.0 * np.ones(idof.shape) )) -# - 'x = 1' : 'u_x = 0.1' -idof = dofs[inode[:,-1], 0] -iip = np.hstack(( iip, idof )) -up = np.hstack(( up , 0.1 * np.ones(idof.shape) )) - -# free displacements -iiu = np.setdiff1d(dofs.ravel(), iip) - -# partition -# - stiffness matrix -Kuu = K.tocsr()[iiu,:].tocsc()[:,iiu] -Kup = K.tocsr()[iiu,:].tocsc()[:,iip] -Kpu = K.tocsr()[iip,:].tocsc()[:,iiu] -Kpp = K.tocsr()[iip,:].tocsc()[:,iip] -# - external force -fu = f[iiu] - -# solve -uu = spsolve(Kuu, fu - Kup.dot(up)) -fp = Kpu.dot(uu) + Kpp.dot(up) - -# assemble -u[iiu] = uu -u[iip] = up -f[iip] = fp - -# convert to nodal displacement and forces -disp = u[dofs] -fext = f[dofs] - -# ================================================================================================== -# compute strain -# ================================================================================================== - -# strain per integration point: allocate -Eps = np.zeros((nelem,nip,nd,nd),dtype='float') - -# loop over nodes -for ielem, e in enumerate(conn): - - # - nodal coordinates and displacements - xe = coor[e,:] - ue = disp[e,:] - - # - allocate element stiffness matrix - Ke = np.zeros((nne*nd, nne*nd)) - - # - numerical quadrature - for k, (w, xi) in enumerate(zip(W, Xi)): - - # -- shape function gradients (w.r.t. the local element coordinates) - dNdxi = np.array([ - [-.25*(1.-xi[1]), -.25*(1.-xi[0])], - [+.25*(1.-xi[1]), -.25*(1.+xi[0])], - [+.25*(1.+xi[1]), +.25*(1.+xi[0])], - [-.25*(1.+xi[1]), +.25*(1.-xi[0])], - ]) - - # -- Jacobian - J = np.zeros((nd, nd)) - - for m in range(nne): - for i in range(nd): - for j in range(nd): - J[i,j] += dNdxi[m,i] * xe[m,j] - - Jinv = np.linalg.inv(J) - Jdet = np.linalg.det(J) - - # -- shape function gradients (w.r.t. the global coordinates) - dNdx = np.zeros((nne,nd)) - - for m in range(nne): - for i in range(nd): - for j in range(nd): - dNdx[m,i] += Jinv[i,j] * dNdxi[m,j] - - # -- displacement gradient - gradu = np.zeros((nd,nd)) - - for m in range(nne): - for i in range(nd): - for j in range(nd): - gradu[i,j] += dNdx[m,i] * ue[m,j] - - # -- strain - eps = .5 * ( gradu + gradu.T ) - - # -- assemble - Eps[ielem,k,:,:] = eps - -# ================================================================================================== -# plot -# ================================================================================================== - -import matplotlib.pyplot as plt - -fig, axes = plt.subplots(ncols=2, figsize=(16,8)) - -axes[0].scatter(coor[:,0], coor[:,1]) -axes[0].quiver (coor[:,0], coor[:,1], disp[:,0], disp[:,1]) - -axes[1].imshow(Eps[:,:,0,0].reshape(ny*int(nip/nd), nx*int(nip/nd)), clim=(0,.2)) - -for axis in axes: - axis.set_xlim([-0.4, 1.5]) - axis.set_ylim([-0.4, 1.5]) - -plt.show() diff --git a/docs/theory/goose-article.cls b/docs/theory/goose-article.cls index c3484e4..efe05b0 100644 --- a/docs/theory/goose-article.cls +++ b/docs/theory/goose-article.cls @@ -1,290 +1,321 @@ % ------------------------------------------------------------------------------ % @LaTeX-class-file{ % filename = "goose-article.cls", -% version = "0.3", -% date = "16 November 2016", +% version = "0.1.1", +% date = "1 April 2018", % codetable = "ISO/ASCII", % keywords = "LaTeX, goose-article", -% supported = "http://tdegeus.github.io/GooseLaTeX", +% supported = "https://github.com/tdegeus/goose-article", % docstring = "A customized article-class." % ------------------------------------------------------------------------------ % ============================================================================== % Basic settings % ============================================================================== \NeedsTeXFormat{LaTeX2e} \ProvidesClass{goose-article}[2016/11/16 v0.3 customized article class] % load default article class \LoadClass[a4paper,fleqn,twoside]{article} % ============================================================================== % Options % ============================================================================== % bibliography [namecite] % ----------------------- \newif\if@namecite \let\if@namecite\iffalse \DeclareOption{namecite}{\let\if@namecite\iftrue} % Review: [narrow,doublespacing] % ------------------------------ \newif\if@narrow \let\if@narrow\iffalse \DeclareOption{narrow}{\let\if@narrow\iftrue} \newif\if@doublespacing \let\if@doublespacing\iffalse \DeclareOption{doublespacing}{\let\if@doublespacing\iftrue} +\newif\if@twocolumn +\let\if@twocolumn\iffalse +\DeclareOption{twocolumn}{\let\if@twocolumn\iftrue} + +% No header +% --------- + +\newif\if@empty +\let\if@empty\iffalse +\DeclareOption{empty}{\let\if@empty\iftrue} + % Font: [times,garamond,verdana] % ------------------------------ \newif\if@font \let\if@font\iffalse \newif\if@times \let\if@times\iffalse \DeclareOption{times}{\let\if@times\iftrue\let\if@font\iftrue} \newif\if@garamond \let\if@garamond\iffalse \DeclareOption{garamond}{\let\if@garamond\iftrue\let\if@font\iftrue} \newif\if@verdana \let\if@verdana\iffalse \DeclareOption{verdana}{\let\if@verdana\iftrue\let\if@font\iftrue} % Process options % --------------- \ProcessOptions\relax % ============================================================================== % Packages % ============================================================================== \RequirePackage[left=25mm,right=25mm,top=30mm,bottom=30mm]{geometry} \if@narrow \newgeometry{left=40mm,right=40mm,top=30mm,bottom=30mm} \fi \RequirePackage{fix-cm} \RequirePackage{graphicx} \RequirePackage[labelformat=simple]{subcaption} \RequirePackage{amssymb,amsfonts,bm,cancel} \RequirePackage{enumerate} \RequirePackage[fleqn]{amsmath} \RequirePackage[font={small},labelfont=bf,labelsep=period]{caption} \RequirePackage[bf]{titlesec} \RequirePackage[dvipsnames]{xcolor} \RequirePackage{authblk} \RequirePackage{hyperref} +\RequirePackage{multicol} \renewcommand\thesubfigure{(\alph{subfigure})} % ============================================================================== % Citation style % ============================================================================== \if@namecite \RequirePackage{natbib} \let\oldbibliography\bibliography \renewcommand{\bibliography}[1]{% \bibliographystyle{apalike} \setlength{\bibsep}{3pt plus 0.3ex} \def\bibfont{\scriptsize} \oldbibliography{#1} } \else \RequirePackage[square,sort&compress,numbers,comma]{natbib} \let\oldbibliography\bibliography \renewcommand{\bibliography}[1]{% \bibliographystyle{unsrtnat} \setlength{\bibsep}{3pt plus 0.3ex} \def\bibfont{\scriptsize} - \oldbibliography{#1} + \begin{multicols}{2}\raggedright\oldbibliography{#1}\end{multicols} } \fi % define DOI command \newcommand*{\doi}[1]{\sloppy\href{http://dx.doi.org/#1}{doi:~#1}} -\newcommand*{\eprint}[1]{\sloppy\href{http://arxiv.org/abs/#1}{arXiv:~#1}} +\newcommand*{\arxivid}[1]{\sloppy\href{http://arxiv.org/abs/#1}{arXiv:~#1}} % ============================================================================== % Font % ============================================================================== \if@font \RequirePackage{mathspec} \RequirePackage{fontspec} \fi \if@garamond \AtBeginDocument{% \setmathfont(Digits)[Numbers=OldStyle]{Garamond}% \setromanfont[% BoldFont={Garamond Bold},% ItalicFont={Garamond Italic},% Mapping=tex-text% ]{Garamond}% }% \fi \if@verdana \AtBeginDocument{% \defaultfontfeatures{Mapping=tex-text} \setmainfont[Mapping=tex-text]{Verdana} \setsansfont{Verdana} \renewcommand*{\familydefault}{\sfdefault} \setmathfont(Greek,Digits,Latin){Verdana} \setmathrm{Verdana} }% \fi \if@times \AtBeginDocument{% \setmathfont(Digits)[Numbers=OldStyle]{Times New Roman}% \setromanfont[% BoldFont={Times New Roman Bold},% ItalicFont={Times New Roman Italic},% Mapping=tex-text% ]{Times New Roman}% }% \fi % ============================================================================== % Spacing % ============================================================================== \if@doublespacing \AtBeginDocument{% \usepackage{setspace} \doublespacing }% \fi +% ============================================================================== +% Two-columns +% ============================================================================== + +\if@twocolumn + \AtBeginDocument{% + \twocolumn + }% +\fi + % ============================================================================== % Hyperref % ============================================================================== \AtBeginDocument{% \hypersetup{% pdftitle=\@title, citecolor=Blue, filecolor=black, linkcolor=Blue, urlcolor=Blue, breaklinks, hidelinks, bookmarksopen=true, }% } % ============================================================================== % Headers % ============================================================================== % {command}{format}{label}{sep}{before-code}{after-code} % - section \titleformat {\section} {\normalfont\large\bfseries} {\thesection} {1em} {} {} % - subsection \titleformat {\subsection} {\normalfont\normalsize\bfseries} {\thesubsection} {1em} {} {} % - paragraph \titleformat {\paragraph} {\normalfont\normalsize\bfseries} {} {} {} + {} % spacing {left}{top}{bottom}, of: % - section \titlespacing{\section} {0pt}{1eM}{0.8em} % - subsection \titlespacing{\subsection} {0pt}{0.8em}{0.4em} +% - paragraph +\titlespacing{\paragraph} + {0pt}{0.8em}{0em} % no paragraph indent after header \AtBeginDocument{% \makeatletter \let\orig@afterheading\@afterheading \def\@afterheading{% \@afterindentfalse \orig@afterheading} \makeatother } % ============================================================================== % Title % ============================================================================== % title, authors, affiliation layout % - contact \newcommand{\contact}[1]{\date{{#1}}} % - basic settings \newcommand{\nl}{\vspace*{-.3em}\protect\\ \fontsize{9}{10.8}\itshape} \renewcommand\Authfont{\fontsize{11}{14.4}\selectfont} \renewcommand\Affilfont{\fontsize{9}{10.8}\itshape} \renewcommand*{\Authsep}{, } \renewcommand*{\Authand}{, } \renewcommand*{\Authands}{, } % - actual layout \renewcommand{\maketitle}{% \newpage \null \vskip 2em% \begin{center}% \let \footnote \thanks {\Large\bfseries \@title \par}% \vskip 1.5em% {\normalsize \lineskip .5em% \begin{tabular}[t]{c}% \@author \end{tabular}\par}% \vskip 0.5em% {\small \@date}% \end{center}% \par \vskip 1.5em \thispagestyle{fancy} } % ============================================================================== % Header and footer % ============================================================================== \RequirePackage{fancyhdr} \setlength{\headheight}{16pt} \pagestyle{fancy} \fancyhead{} \fancyfoot{} \renewcommand{\headrulewidth}{0pt} \renewcommand{\footrulewidth}{0pt} -\fancyhead[RO,LE]{\scriptsize \thepage} -\newcommand{\header}[1]{\fancyhead[LO,RE]{\scriptsize\itshape#1}} +\if@empty +\else +\fancyhead[RO,LE]{\thepage} +\fi +\newcommand{\header}[1]{\fancyhead[LO,RE]{#1}} +\newcommand{\headerOdd}[1]{\fancyhead[LO]{#1}} +\newcommand{\headerEven}[1]{\fancyhead[RE]{#1}} % ============================================================================== % Abstract and keywords % ============================================================================== \renewenvironment{abstract}{\section*{Abstract}\noindent\ignorespaces}{\ignorespacesafterend} \newcommand{\keywords}[1]{\vspace*{0.5eM}\noindent\textbf{Keywords: }#1} diff --git a/docs/theory/readme.pdf b/docs/theory/readme.pdf index cb45de0..b7802e5 100644 Binary files a/docs/theory/readme.pdf and b/docs/theory/readme.pdf differ diff --git a/docs/theory/readme.tex b/docs/theory/readme.tex index 1967279..b9b4735 100644 --- a/docs/theory/readme.tex +++ b/docs/theory/readme.tex @@ -1,1171 +1,1192 @@ %!TEX program = xelatex \documentclass[times,namecite]{goose-article} \usepackage{lipsum,verbatim,mdframed,array} \title{% The Finite Element Method in Continuum Mechanics } \author{T.W.J.~de~Geus} \contact{% $^*$Contact: % \href{http://goosefem.geus.me}{goosefem.geus.me}% \hspace{1mm}--\hspace{1mm} % \href{mailto:tom@geus.me}{tom@geus.me} % \hspace{1mm}--\hspace{1mm} % \href{http://www.geus.me}{www.geus.me}% } \hypersetup{pdfauthor={T.W.J. de Geus}} -\header{% +\headerEven{\emph{% The Finite Element Method in Continuum Mechanics, T.W.J.\ de Geus -} +}} \begin{document} \maketitle \begin{abstract} The theory of the Finite Element Method in Continuum Mechanics is discussed in a compact way. This discussion is by no means comprehensive and one is invited to dive in more complete textbooks, but also to get one's hands dirty by implementing some (simple) examples. \end{abstract} \setcounter{tocdepth}{2} \tableofcontents \section{Statics} \subsection{The basic concept} -The discussion starts by considering a static, solid mechanics, problem. Loosely speaking the goal is to find a deformation map, $\vec{x} = \varphi(\vec{X},t)$, that maps a body $\Omega_0$ to a deformed state $\Omega$ that satisfies equilibrium and the boundary conditions applied on $\Gamma$. This is illustrated in Fig.~\ref{fig:problem}, whereby it is emphasized that the body can be subjected to two kinds of boundary conditions: +The discussion starts by considering a static, solid mechanics, problem. Loosely speaking the goal is to find a deformation map, $\vec{x} = \varphi(\vec{X},t)$, that maps a body $\Omega_0$ to a deformed state $\Omega$ that satisfies equilibrium \emph{and} the boundary conditions applied on $\Gamma$. This is illustrated in Fig.~\ref{fig:problem}, whereby it is emphasised that the body can be subjected to two kinds of boundary conditions: \begin{itemize} \item \emph{Essential} or \emph{Dirichlet} boundary conditions on $\Gamma_p$, whereby the displacements are prescribed. \item \emph{Natural} or \emph{Neumann} boundary conditions on $\Gamma_u$, whereby the tractions are prescribed. Note that traction-free natural boundary conditions are also perfectly acceptable. \end{itemize} \begin{figure}[htp] \centering \includegraphics[width=.5\textwidth]{figures/problem.pdf} \caption{Generic problem definition} \label{fig:problem} \end{figure} In practice, one is not looking for the map $\vec{x} = \varphi(\vec{X},t)$, but for the deformation gradient $\bm{F}$, or in fact for a displacement field $\vec{u} = \vec{x} - \vec{X}$. To make things a bit more explicit, the deformation gradient is defined as follows: \begin{equation} \vec{x} = \bm{F} \cdot \vec{X} \end{equation} hence \begin{equation} \bm{F} = \frac{\partial \varphi}{\partial \vec{X}} = \big( \vec{\nabla}_0 \, \vec{x} \big)^T = \bm{I} + \big( \vec{\nabla}_0 \, \vec{u} \big)^T \end{equation} \subsection{Momentum balance} The momentum balance reads \begin{equation} \label{eq:static:momentum} \vec{\nabla} \cdot \bm{\sigma}(\vec{x}) = \vec{0} \qquad \vec{x} \in \Omega \end{equation} where $\bm{\sigma}$ is the Cauchy stress which depends on the new position $\vec{x}$ and thus on the displacement $\vec{u}$. It has been assumed that all actions are instantaneous (no inertia) and, for simplicity, that there are no body forces. Loosely speaking the interpretation of this equation is that \emph{the sum of all forces vanishes} everywhere in the domain $\Omega$. The crux of the Finite Element Method is that this non-linear differential equation is solved in a weak sense. I.e. \begin{equation} \label{eq:static:int} \int\limits_\Omega \vec{\phi}(\vec{x}) \cdot \big[\, \vec{\nabla} \cdot \bm{\sigma}(\vec{x}) \,\big] \; \mathrm{d}\Omega = 0 \qquad \forall \; \vec{\phi}(\vec{x}) \in \mathbb{R}^d \end{equation} where $\vec{\phi}$ are test functions. For reasons that become obvious below, integration by parts is applied, which results in \begin{equation} \label{eq:static:weak} \int\limits_\Omega \big[\, \vec{\nabla} \vec{\phi}(\vec{x}) \,\big] : \bm{\sigma}(\vec{x}) \; \mathrm{d}\Omega = \int\limits_\Omega \vec{\nabla} \cdot \big[\, \vec{\phi}(\vec{x}) \cdot \bm{\sigma}(\vec{x}) \,\big] \; \mathrm{d}\Omega \qquad \forall \; \vec{\phi}(\vec{x}) \in \mathbb{R}^d \end{equation} In going from Eq.~\eqref{eq:static:int} to Eq.~\eqref{eq:static:weak}, use has been made of the following chain rule \begin{equation} \vec{\nabla} \cdot \big[\, \vec{\phi} \cdot \bm{\sigma}^T \,\big] = \big[\, \vec{\nabla} \vec{\phi} \,\big] : \bm{\sigma}^T + \vec{\phi} \cdot \big[\, \vec{\nabla} \cdot \bm{\sigma} \,\big] \end{equation} -together with the symmetry of the Cauchy stress +Note thereby that the Cauchy stress tensor is symmetric: \begin{equation} \bm{\sigma} = \bm{\sigma}^T \end{equation} The right-hand side of this equation can be reduced to an area integral by employing Gauss's divergence theorem: \begin{equation} - \int\limits_\Omega \vec{\nabla} \cdot \vec{a}(\vec{x}) \; \mathrm{d}\Omega + \int\limits_\Omega \vec{\nabla} \cdot \vec{t}(\vec{x}) \; \mathrm{d}\Omega = - \int\limits_\Gamma \vec{n}(\vec{x}) \cdot \vec{a}(\vec{x}) \; \mathrm{d}\Gamma + \int\limits_\Gamma \vec{n}(\vec{x}) \cdot \vec{t}(\vec{x}) \; \mathrm{d}\Gamma \end{equation} where $\vec{n}$ is the normal along the surface $\Gamma$. The result reads \begin{equation} \label{eq:static:weak:final} \int\limits_\Omega \big[\, \vec{\nabla} \vec{\phi}(\vec{x}) \,\big] : \bm{\sigma}(\vec{x}) \; \mathrm{d}\Omega = \int\limits_\Gamma \vec{\phi}(\vec{x}) \cdot \underbrace{ \vec{n}(\vec{x}) \cdot \bm{\sigma}(\vec{x}) }_{ \vec{t}(\vec{x}) } \; \mathrm{d}\Gamma \qquad \forall \; \vec{\phi}(\vec{x}) \in \mathbb{R}^d \end{equation} -\subsection{Discretization} +\subsection{Discretisation} -The problem is now discretized using $n$ \emph{nodes} that are connected through \emph{elements}, which define the discretized domain $\Omega^h_0$. Shape functions $N_i(\vec{x})$ are used to extrapolate the nodal quantities throughout the domain $\Omega^h_0$ (and $\Omega^h$). For example the displacement field $\vec{u}(\vec{x})$ +The problem is now discretised using $n$ \emph{nodes} that are connected through \emph{elements}, which define the discretised domain $\Omega^h_0$. Shape functions $N_i(\vec{x})$ are used to interpolate the nodal quantities throughout the domain $\Omega^h_0$ (and $\Omega^h$). For example, the discretisation and interpolation of the displacement field reads \begin{equation} \vec{u}(\vec{x},t) \approx \vec{u}^h(\vec{x},t) = \sum_{m=1}^{n_\mathrm{nodes}} N_m (\vec{x}) \; \vec{u}_m (t) \end{equation} whereby we will not further consider the time dependence (denoted with $t$) for now. Following standard Galerkin the test functions are interpolated in the same way, i.e.\ \begin{equation} -\label{eq:discretization} +\label{eq:discretisation} \vec{\phi}(\vec{x}) \approx \vec{\phi}^h(\vec{x}) = \sum_{m=1}^{n_\mathrm{nodes}} N_m (\vec{x}) \; \vec{\phi}_m \end{equation} -Applied to the problem sketch from Fig.~\ref{fig:problem}, a discretization might look like Fig.~\ref{fig:problem:discretized}. The nodes are clearly marked as circles. The lines connecting the nodes clearly mark the elements which are in this case three-node linear triangles. +Applied to the problem sketch from Fig.~\ref{fig:problem}, a discretisation might look like Fig.~\ref{fig:problem:discretised}. The nodes are clearly marked as circles. The lines connecting the nodes clearly mark the elements which are in this case three-node linear triangles. \begin{figure}[htp] \centering \includegraphics[width=.25\textwidth]{figures/problem-discretized.pdf} - \caption{Example of a discretization of the problem in Fig.~\ref{fig:problem} using Finite Elements (linear triangles in this case).} - \label{fig:problem:discretized} + \caption{Example of a discretisation of the problem in Fig.~\ref{fig:problem} using Finite Elements (linear triangles in this case).} + \label{fig:problem:discretised} \end{figure} Applied to the balance equation the following is obtained \begin{equation} \vec{\phi}_m \cdot \int\limits_{\Omega^h} \big[\, \vec{\nabla} N_m (\vec{x}) \,\big] \cdot \bm{\sigma}(\vec{x}) \; \mathrm{d}\Omega = \vec{\phi}_m \cdot \int\limits_{\Gamma^h} N_m (\vec{x}) \cdot \vec{t}(\vec{x}) \; \mathrm{d}\Gamma \qquad \forall \; \vec{\phi}_m \in \mathbb{R}^d_n \end{equation} from which the dependency on $\vec{\phi}_m$ can be dropped: \begin{equation} \int\limits_{\Omega^h} \big[\, \vec{\nabla} N_m (\vec{x}) \,\big] \cdot \bm{\sigma}(\vec{x}) \; \mathrm{d}\Omega = \int\limits_{\Gamma^h} N_m (\vec{x}) \cdot \vec{t}(\vec{x}) \; \mathrm{d}\Gamma \end{equation} This corresponds to a (non-linear) set of nodal balance equations: \begin{equation} \vec{f}_m^\mathrm{int}(\vec{x}) = \vec{f}_m^\mathrm{ext}(\vec{x}) \end{equation} with: \begin{itemize} \item \emph{Internal forces} \begin{equation} \vec{f}_m^\mathrm{int}(\vec{x}) = \int\limits_{\Omega^h} \big[\, \vec{\nabla} N_m (\vec{x}) \,\big] \cdot \bm{\sigma}(\vec{x}) \; \mathrm{d}\Omega \end{equation} \item \emph{External forces} \begin{equation} \vec{f}_m^\mathrm{ext}(\vec{x}) = \int\limits_{\Gamma^h} N_m (\vec{x}) \cdot \vec{t}(\vec{x}) \; \mathrm{d}\Gamma \end{equation} - Note that this term is zero in the interior of the domain, i.e. in $\Omega^h \bigcap \Gamma^h$, while it can be zero or non-zero in $\Gamma^h$ depending on the problem details. Most often it following completely from the boundary conditions of the boundary value problem that we set out to solve. + Note that this term is zero in the interior of the domain, i.e. in $\Omega^h \bigcap \Gamma^h$, while it can be zero or non-zero in $\Gamma^h$ depending on the problem details. Most often it follows completely from the boundary conditions of the boundary value problem that we set out to solve. \end{itemize} \subsection{Iterative solution -- small strain} A commonly used strategy to solve the non-linear system is the iterative Newton-Raphson scheme (see Appendix~\ref{sec:newton-raphson}). The idea is thereby to formulate an initial guess for the solution, determine possible residual forces, and use these forces to come to a better guess for the solution. This is continued until the solution has been found, i.e. when the residual vanishes. -This solution technique is discussed here in the context of small deformations. Assuming the deformations (and therefore rotations) to be small leads to the assumption that $\Omega = \Omega_0$, and thus that $\nabla = \nabla_0$. In this context one typically uses the linear strain tensor +This solution technique is discussed here in the context of small deformations. Assuming the deformations (and therefore rotations) to be small, leads to the assumption that $\Omega = \Omega_0$, and thus that $\nabla = \nabla_0$. In this context one typically uses the linear strain tensor \begin{equation} \bm{\varepsilon} = \tfrac{1}{2} \left[ \nabla_0 \vec{u} + \big[\, \nabla_0 \vec{u} \,\big]^T \right] = \mathbb{I}_s : \big[\, \nabla_0 \vec{u} \,\big] \end{equation} and some (non-linear) relationship between it and the stress as follows \begin{equation} \bm{\sigma} = \bm{\sigma} \big( \bm{\varepsilon} \big) \end{equation} To further simplify the discussion, the boundary tractions are assumed to be some displacement independent quantity, which is \textit{a-priori} known for all relevant boundary nodes. The nodal equilibrium equations now reads \begin{equation} \label{eq:static:eq-non-lin} \vec{r}_m(\vec{X}, \vec{u}) = \vec{f}_m^\mathrm{ext}(\vec{X}) - \vec{f}_m^\mathrm{int}(\vec{X}, \vec{u}) = \underline{\vec{0}} \end{equation} with \begin{equation} \vec{f}_m^\mathrm{int}(\vec{X}, \vec{u}) = \int\limits_{\Omega^h_0} \big[\, \vec{\nabla}_0 N_m(\vec{X}) \,\big] \cdot \bm{\sigma}(\vec{X}, \vec{u}) \; \mathrm{d}\Omega_0 \end{equation} The displacement field $\vec{u}$ is now iteratively updated starting from some initial guess, i.e. \begin{equation} \vec{u}_{(i+1)} = \vec{u}_{(i)} + \delta \vec{u} \end{equation} The update, $\delta \vec{u}$, follows from the linearization of Eq.~\eqref{eq:static:eq-non-lin}. The results of which is \begin{equation} \int\limits_{\Omega^h_0} \big[\, \vec{\nabla}_0 N_m(\vec{X}) \,\big] \cdot \mathbb{K}\big(\vec{X},\vec{u}_{(i)}\big) \cdot \big[\, \vec{\nabla}_0 N_n(\vec{X}) \,\big] \; \mathrm{d}\Omega_0 \cdot \delta \vec{u}_n = \vec{f}_m^\mathrm{ext}(\vec{X}) - \int\limits_{\Omega^h_0} \big[\, \vec{\nabla}_0 N_m(\vec{X}) \,\big] \cdot \bm{\sigma}\big(\vec{X},\vec{u}_{(i)}\big) \; \mathrm{d}\Omega_0 \end{equation} where, at each position $\vec{X}$, \begin{equation} \mathbb{K}\big(\vec{u}_{(i)}\big) = \left. \frac{\partial \bm{\sigma}}{\partial \bm{\varepsilon}} \right|_{\vec{u}_{(i)}} : \mathbb{I}_s \end{equation} is the \emph{constitutive tangent operator}. In a shorter notation, the iterative update reads: \begin{equation} \bm{K}_{mn,(i)} \cdot \delta \vec{u}_m = \vec{f}_m^\mathrm{ext} - \vec{f}_{m,(i)}^\mathrm{int} \end{equation} with \begin{equation} \bm{K}_{mn,(i)} = \int\limits_{\Omega^h_0} \big[\, \vec{\nabla}_0 N_m(\vec{X}) \,\big] \cdot \mathbb{K}\big(\vec{X}, \vec{u}_{(i)}\big) \cdot \big[\, \vec{\nabla}_0 N_m(\vec{X}) \,\big] \; \mathrm{d}\Omega_0 \end{equation} and \begin{equation} \vec{f}_{m,(i)}^\mathrm{int} = \int\limits_{\Omega^h_0} \big[\, \vec{\nabla}_0 N_m(\vec{X}) \,\big] \cdot \bm{\sigma}\big(\vec{X}, \vec{u}_{(i)}\big) \; \mathrm{d}\Omega_0 \end{equation} \section{Dynamics} \subsection{Momentum balance} The next step is to add inertia to the balance of momentum in Eq.~\eqref{eq:static:momentum}: \begin{equation} \rho\, \vec{a} = \vec{\nabla} \cdot \bm{\sigma}(\vec{x}) \qquad \vec{x} \in \Omega \end{equation} where $\rho$ is the mass density, and second time derivative of the position $\vec{x}$ is the acceleration $\vec{a} = \ddot{\vec{u}} = \ddot{\vec{x}}$. Note that this function is the continuum equivalent of $\vec{f} = m \vec{a}$. Like before, this equation is solved in a weak sense \begin{equation} \int\limits_\Omega \rho(\vec{x})\; \vec{\phi}(\vec{x}) \cdot \vec{a} \; \mathrm{d}\Omega = \int\limits_\Omega \vec{\phi}(\vec{x}) \cdot \Big[\, \vec{\nabla} \cdot \bm{\sigma}(\vec{x}) \,\Big] \; \mathrm{d}\Omega \qquad \forall \; \vec{\phi}(\vec{x}) \in \mathbb{R}^d \end{equation} Integration by parts results in \begin{equation} \int\limits_\Omega \rho(\vec{x})\; \vec{\phi}(\vec{x}) \cdot \vec{a} \; \mathrm{d}\Omega = \int\limits_\Gamma \vec{\phi}(\vec{x}) \cdot \vec{t}(\vec{x}) \; \mathrm{d}\Gamma - \int\limits_\Omega \big[\, \vec{\nabla} \vec{\phi}(\vec{x}) \,\big] : \bm{\sigma}(\vec{x}) \; \mathrm{d}\Omega \qquad \forall \; \vec{\phi}(\vec{x}) \in \mathbb{R}^d \end{equation} -which is discretized as before: +which is discretised as before: \begin{equation} \vec{\phi}_m \cdot \int\limits_\Omega \rho(\vec{x})\; N_m(\vec{x})\; N_n(\vec{x}) \; \mathrm{d}\Omega \; \vec{a}_n = \vec{\phi}_m \cdot \int\limits_\Gamma N_m(\vec{x})\; \vec{t}(\vec{x}) \; \mathrm{d}\Gamma - \vec{\phi}_m \cdot \int\limits_\Omega \big[\, \vec{\nabla} N_m(\vec{x}) \,\big] : \bm{\sigma}(\vec{x}) \; \mathrm{d}\Omega - \qquad - \forall \; \vec{\phi}_m \in \mathbb{R}^d_n \end{equation} -This is independent of the test functions, hence: +which should still hold for all $\vec{\phi}_m \in \mathbb{R}^d_n$. However, this dependence can now trivially be dropped. Hence: \begin{equation} \label{eq:dynamics:system} \underbrace{ \int\limits_\Omega \rho(\vec{x})\; N_m(\vec{x})\; N_n(\vec{x}) \; \mathrm{d}\Omega }_{\displaystyle M_{mn}(\vec{x}) } \; \vec{a}_n = \underbrace{ \int\limits_\Gamma N_m(\vec{x})\; \vec{t}(\vec{x}) \; \mathrm{d}\Gamma }_{\displaystyle \vec{f}_m^\mathrm{ext}(\vec{x}) } - \underbrace{ \int\limits_\Omega \big[\, \vec{\nabla} N_m(\vec{x}) \,\big] : \bm{\sigma}(\vec{x}) \; \mathrm{d}\Omega }_{\displaystyle \vec{f}_m^\mathrm{int}(\vec{x}) } \end{equation} This is denoted as follows \begin{equation} M_{mn}(\vec{x})\; \vec{a}_n = \vec{f}_m^\mathrm{ext}(\vec{x}) - \vec{f}_m^\mathrm{int}(\vec{x}) \end{equation} Where $M_{mn}(\vec{x})$ is the \emph{mass matrix}, $\vec{f}_m^\mathrm{ext}(\vec{x})$ are the \emph{external forces}, and $\vec{f}_m^\mathrm{int}(\vec{x})$ are the \emph{internal forces}. -\subsection{Time discretization} +\subsection{Time discretisation} \label{eq:dynamics:time} -To solve the second order differential equation in time, one typically also discretizes time with some finite difference based scheme. For this we also need to distinguish the velocity field $\vec{v} = \dot{\vec{u}} = \dot{\vec{x}}$. +To solve the second order differential equation in time, one typically also discretises time with some finite difference based scheme. For this we also need to distinguish the velocity field $\vec{v} = \dot{\vec{u}} = \dot{\vec{x}}$. \subsubsection{Verlet} -In the case that the forces are velocity independent one can use the Velocity Verlet scheme, in which energy is conserved, i.e.\ it is reversible in time. The scheme reads are follows: +In the case that the forces are velocity independent one can use the Velocity Verlet scheme, in which energy is conserved, i.e.\ it is reversible in time. The scheme reads as follows: \begin{enumerate} \item Compute the velocity on a dummy time grid: \begin{equation} \vec{v}_{n+1/2} = \vec{v}_{n-1/2} + \Delta_t \; \vec{a}_n \end{equation} Note that this involves solving Eq.~\eqref{eq:dynamics:system} for $\vec{a}_n$. From this it is directly obvious why the forces need to be velocity independent. \item Update the positions \begin{equation} \vec{u}_{n+1} = \vec{u}_n + \Delta_t \;\vec{a} \vec{v}_{n + 1/2} \end{equation} \end{enumerate} \subsubsection{Velocity Verlet} In the case that the forces are dependent on the velocity (i.e.\ when there is damping) the previous scheme cannot be used anymore. An additional approximation has to be made in this case. Note that this is not very problematic as energy is in no case conserved. The resulting scheme reads: \begin{enumerate} \item Compute the position at $t_{n+1} = t_{n} + \Delta_t$: \begin{equation} \vec{u}_{n+1} = \vec{u}_{n} + \Delta_t \vec{v}_{n} + \tfrac{1}{2} \Delta_t^2 \vec{a}_{n} \end{equation} \item Estimate the velocity at $t_{n+1} = t_{n} + \Delta_t$ (involves solving Eq.~\eqref{eq:dynamics:system}): \begin{equation} \hat{\vec{v}}_{n+1} = \vec{v}_{n} + \tfrac{1}{2} \Delta_t \Big[\, \vec{a}_{n} + \vec{a} ( \vec{u}_{n+1} , \vec{v}_{n} + \Delta_t \vec{a}_{n} , t_{n+1} ) \, \Big] \end{equation} \item Correct $\hat{\vec{v}}_{n+1}$ (involves solving Eq.~\eqref{eq:dynamics:system}): \begin{equation} \vec{v}_{n+1} = \vec{v}_{n} + \tfrac{1}{2} \Delta_t \Big[\, \vec{a}_{n} + \vec{a} ( \vec{u}_{n+1} , \hat{\vec{v}}_{n+1} , t_{n+1} ) \, \Big] \end{equation} \end{enumerate} \subsection{Approximations} For problems where the local volume is conversed (either weakly through slightly compressible elasticity, or strongly by adding an incompressibility constraint) it makes sense to assume the mass matrix constant, as any change of volume results in an equivalent change of the density. In that case \begin{equation} \int\limits_{\Omega} \rho(\vec{x}) \;\mathrm{d}\Omega = \int\limits_{\Omega_0} \rho(\vec{X}) \;\mathrm{d}\Omega_0 \end{equation} Which results in: \begin{equation} M_{mn}(\vec{x}) = \int\limits_{\Omega_0} \rho(\vec{X})\; N_m(\vec{X})\; N_n(\vec{X}) \; \mathrm{d}\Omega_0 = \mathrm{constant} \end{equation} To enhance computational efficiency, it may be a good option concentrate the mass to point masses on the nodes. This has to strong advantage that the mass matrix becomes diagonal, known as the \emph{lumped mass matrix}. Consequently, instead of solving a linear system one just has to solve fully independent equations. \appendix \section{Nomenclature} \begin{itemize} \item Vector \begin{equation} \vec{u} = \sum\limits_i u_i \vec{e}_i \end{equation} for example for Cartesian coordinates \begin{equation} \vec{u} = u_x \vec{e}_x + u_y \vec{e}_y + u_z \vec{e}_z \end{equation} % \item Second-order tensor \begin{equation} \bm{A} = \sum\limits_i \sum\limits_j A_{ij} \vec{e}_i \vec{e}_j \end{equation} % \item Fourth-order tensor \begin{equation} \mathbb{A} = \sum\limits_i \sum\limits_j \sum\limits_k \sum\limits_l A_{ijkl} \vec{e}_i \vec{e}_j \vec{e}_k \vec{e}_l \end{equation} % \item Divergence \begin{equation} \vec{\nabla} \cdot \bm{\sigma} = \frac{ \partial \sigma_{ij} }{ \partial x_i } \end{equation} % \item Double tensor contraction \begin{equation} C = \bm{A} : \bm{B} = A_{ij} B_{ji} \end{equation} \end{itemize} \section{Shape functions} -In the Finite Element Method a geometry is discretized using nodes. The nodes are grouped in elements which define the domain $\Omega^h$. The crux of the method is that nodal quantities, for example $\vec{u}_i$, are interpolated throughout the discretized domain $\Omega^h$ using shape functions $N_i (\vec{x})$. The shape functions are polynomials that spanned by the nodes, such that they constitute a partition of unity and satisfy $N_i (\vec{x}_j) = \delta_{ij}$, i.e. it is one in the node $i$, and zero in all other nodes. This implies that, while each shape function is globally supported, $N_i (\vec{x}) \neq 0$ only in the elements containing node $i$. For all other elements it is zero (even in between the nodes). This again implies that $\partial N_i / \partial \vec{x}$ is discontinuous across element boundaries. +In the Finite Element Method a geometry is discretised using nodes. The nodes are grouped in elements which define the domain $\Omega^h$. The crux of the method is that nodal quantities, for example $\vec{u}_m$, are interpolated throughout the discretised domain $\Omega^h$ using shape functions $N_m (\vec{x})$. The shape functions are polynomials that spanned by the nodes, such that they constitute a partition of unity and satisfy $N_m (\vec{x}_n) = \delta_{mn}$, i.e. it is one in the node $i$, and zero in all other nodes. This implies that, while each shape function is globally supported, $N_m (\vec{x}) \neq 0$ only in the elements containing node $i$. For all other elements it is zero (even in between the nodes). This again implies that $\partial N_m / \partial \vec{x}$ is discontinuous across element boundaries. -For a one-dimensional problem comprising four linear elements and five nodes the shape functions are sketched in Fig.~\ref{fig:shape:1d-domain}. To emphasize the global support of each shape function the shape function of node 2 is isolated in Fig.~\ref{fig:shape:1d-domain-2}. As can be seen, node 2 is only non-zero in elements 1 and 2, while it is zero everywhere else. To evaluate for example $f_2$, the internal force one node 2, the integration can be limited to these two elements: +For a one-dimensional problem comprising four linear elements and five nodes, the shape functions are sketched in Fig.~\ref{fig:shape:1d-domain}. To emphasise the global support of each shape function the shape function of node 2 is isolated in Fig.~\ref{fig:shape:1d-domain-2}. As can be seen, node 2 is only non-zero in elements 1 and 2, while it is zero everywhere else. To evaluate for example $f_2$, the internal force one node 2, the integration can be limited to these two elements: \begin{equation} f_2 = \int\limits_{\Omega^{(1)}} \frac{\mathrm{d} N^{(1)}_2}{\mathrm{d} x} \; \sigma (x) \; \mathrm{d}\Omega^{(1)} + \int\limits_{\Omega^{(2)}} \frac{\mathrm{d} N^{(2)}_2}{\mathrm{d} x} \; \sigma (x) \; \mathrm{d}\Omega^{(2)} \end{equation} \begin{figure}[htp] \centering \captionsetup[subfigure]{justification=centering} \begin{minipage}[t]{.5\textwidth} \centering \includegraphics[width=1.\textwidth]{figures/shape-functions-1d.pdf} - \subcaption{All shape functions $N_i(x)$, each with a different color.} + \subcaption{All shape functions $N_m(x)$, each with a different colour.} \label{fig:shape:1d-domain} \end{minipage} \\ \begin{minipage}[t]{.5\textwidth} \centering \includegraphics[width=1.\textwidth]{figures/shape-functions-1d-node-2.pdf} \subcaption{Isolated shape function $N_2(x)$.} \label{fig:shape:1d-domain-2} \end{minipage} - \caption{(a) Shape functions for a one-dimensional domain discretized using four two node, linear, element. (b) Isolated shape functions for node 2. The node numbers are displayed using a unique color, while the element numbers are shown in black in a different font, in between the nodes.} + \caption{(a) Shape functions for a one-dimensional domain discretised using four two node, linear, element. (b) Isolated shape functions for node 2. The node numbers are displayed using a unique colour, while the element numbers are shown in black in a different font, in between the nodes.} \label{fig:shape:1d-domain:fig} \end{figure} -By now it should be clear that the above allows us assemble $\underline{f}$ element-by-element. For the example in Fig.~\ref{fig:shape:1d-domain:fig} this is graphically shown in Fig.~\ref{fig:shape:1d-domain:assembly}. +By now it should be clear that the above allows the global system $f_m$ to be assembled element-by-element. For the example in Fig.~\ref{fig:shape:1d-domain:fig} this is graphically shown in Fig.~\ref{fig:shape:1d-domain:assembly} where the indices show that the \emph{shape functions} are evaluated compared to some generic element definition. \begin{figure}[htp] \centering \captionsetup[subfigure]{justification=centering} \begin{minipage}[t]{.5\textwidth} \centering \includegraphics[width=1.\textwidth]{figures/shape-functions-1d-element-0.pdf} \end{minipage} \\ $+$ \\ \begin{minipage}[t]{.5\textwidth} \centering \includegraphics[width=1.\textwidth]{figures/shape-functions-1d-element-1.pdf} \end{minipage} \\ $+$ \\ \begin{minipage}[t]{.5\textwidth} \centering \includegraphics[width=1.\textwidth]{figures/shape-functions-1d-element-2.pdf} \end{minipage} \\ $+$ \\ \begin{minipage}[t]{.5\textwidth} \centering \includegraphics[width=1.\textwidth]{figures/shape-functions-1d-element-3.pdf} \end{minipage} \caption{Element-by-element assembly of the problem in Fig.~\ref{fig:shape:1d-domain:fig}.} \label{fig:shape:1d-domain:assembly} \end{figure} - -where the indices show that the \emph{shape functions} are evaluated compared to some generic element definition. - \section{Iso-parametric transformation and quadrature} A very important concept in the Finite Element Method is the iso-parametric transformation. It allows an arbitrarily shaped element with volume $\Omega^e$ to be mapped onto a generic \emph{iso-parametric element} of constant volume $Q$, see Fig.~\ref{fig:isoparametric}. Using this mapping it is easy to perform interpolation and numerical quadrature using a generic implementation. \begin{figure}[htp] \centering \includegraphics[width=.5\textwidth]{figures/isoparametric-transform.pdf} - \caption{Iso-parameteric transformation for a four node, bi-linear, quadrilateral element.} + \caption{Iso-parametric transformation for a four node, bi-linear, quadrilateral element.} \label{fig:isoparametric} \end{figure} The mapping between the generic domain $Q$ and the physical domain $\Omega^e$ is as follows \begin{equation} \label{eq:isoparametric:coordinate} - \vec{x} ( \vec{\xi} ) = N_m^{e} \; \underline{x}_m^e + \vec{x} ( \vec{\xi} ) = N_m^{e} \; \vec{x}_m^e \end{equation} -where the column $\underline{x}^e$ contains the position vectors of the element nodes in the actual coordinates (i.e.\ spanning $\Omega^e$). In order to perform the quadrature on $Q$ we must also map the gradient operator: +where the column $\vec{x}_m^e$ contains the position vectors of the element nodes in the actual coordinates (i.e.\ spanning $\Omega^e$). In order to perform the quadrature on $Q$ we must also map the gradient operator: \begin{equation} \vec{\nabla}_{\xi}\, = \vec{e}_i \frac{\partial}{\partial \xi_i} = \vec{e}_i \frac{\partial x_j(\vec{\xi})}{\partial \xi_i} \frac{\partial}{\partial x_j} = \vec{e}_i \frac{\partial x_j(\vec{\xi})}{\partial \xi_i} \vec{e}_j \cdot \vec{e}_k \frac{\partial}{\partial x_k} = \big[\, \vec{\nabla}_{\xi}\, \vec{x}(\vec{\xi}) \,\big] \cdot \vec{\nabla} = \bm{J}(\vec{\xi}) \cdot \vec{\nabla} \end{equation} i.e. \begin{equation} J_{ij} = \frac{\partial x_i}{\partial \xi_j} \end{equation} from which the inverse relationship trivially follows \begin{equation} \vec{\nabla} = \bm{J}^{-1}(\vec{\xi}) \cdot \vec{\nabla}_{\xi}\, \end{equation} where $\bm{J}$ is the Jacobian which can be obtained from the gradient of the shape functions with respect to the iso-parametric coordinates and the nodal coordinates in the actual coordinates through Eq.~\eqref{eq:isoparametric:coordinate}: \begin{equation} \bm{J}(\vec{\xi}) = \vec{\nabla}_{\xi}\, \vec{x}(\vec{\xi}) = \vec{\nabla}_{\xi}\, \big[\, N_m^{e} \; \vec{x}_m^e \,\big] = \big[\, \vec{\nabla}_{\xi}\, N_m^{e} \,\big] \; \vec{x}_m^e \end{equation} The gradient of the shape functions with respect to the actual coordinates can now be computed though \begin{equation} \vec{\nabla} N_m^{e} = \bm{J}^{-1}(\vec{\xi}) \cdot \big[\, \vec{\nabla}_{\xi}\, N_m^{e} \,\big] \end{equation} The change of variables should also be accounted for in the integration: \begin{equation} \int_\Omega (\ldots) \; \mathrm{d} \Omega = \iiint (\ldots) \; \mathrm{d} x \, \mathrm{d} y \, \mathrm{d} z = \iiint (\ldots) \; \left| \frac{\partial \left( x, y, z \right)}{\partial \left( \xi, \eta, \mu \right)} \right| \mathrm{d} \xi \, \mathrm{d} \eta \, \mathrm{d} \mu \end{equation} where the determinant of the Jacobian \begin{equation} \frac{\partial \left( x, y, z \right)}{\partial \left( \xi, \eta, \mu \right)} = \left| \begin{array}{ccc} \displaystyle \frac{\partial x}{\partial \xi } & \displaystyle \frac{\partial x}{\partial \eta} & \displaystyle \frac{\partial x}{\partial \mu } \\ \displaystyle \frac{\partial y}{\partial \xi } & \displaystyle \frac{\partial y}{\partial \eta} & \displaystyle \frac{\partial y}{\partial \mu } \\ \displaystyle \frac{\partial z}{\partial \xi } & \displaystyle \frac{\partial z}{\partial \eta} & \displaystyle \frac{\partial z}{\partial \mu } \end{array} \right| \end{equation} We can now make use of the Jacobian that we already obtained \begin{equation} \int_\Omega (\ldots) \; \mathrm{d} \Omega = \iiint (\ldots) \det \left( \bm{J}(\vec{\xi}) \right) \; \mathrm{d} \xi \, \mathrm{d} \eta \, \mathrm{d} \mu = \int_Q (\ldots) \det \left( \bm{J}(\vec{\xi}) \right) \; \mathrm{d} Q \end{equation} Note that another way to obtain this result is using \begin{align} \mathrm{d} \Omega &= \mathrm{d} \vec{x}_0 \times \mathrm{d} \vec{x}_1 \cdot \mathrm{d} \vec{x}_2 = \left[ \mathrm{d} \vec{x}_0 \cdot \bm{J}(\vec{\xi}) \right] \times \left[ \mathrm{d} \vec{x}_1 \cdot \bm{J}(\vec{\xi}) \right] \cdot \left[ \mathrm{d} \vec{x}_2 \cdot \bm{J}(\vec{\xi}) \right] = \det \big( \bm{J}(\vec{\xi}) \big)\, \mathrm{d} \vec{\xi}_0 \times \mathrm{d} \vec{\xi}_1 \cdot \mathrm{d} \vec{\xi}_2 \\ &= \det \big( \bm{J}(\vec{\xi}) \big)\, \mathrm{d} Q \end{align} -Numerical quadrature can be formulated (exactly) on the master element. It corresponds to taking the weighted sum of the integrand evaluated at specific \emph{quadrature points} (or \emph{integration points}). - -The practical implications, for example for the internal force, are as follows: +Numerical quadrature can be formulated (exactly) on the master element. It corresponds to taking the weighted sum of the integrand evaluated at specific \emph{quadrature points} (or \emph{integration points}). The practical implications, for example for the internal force, are as follows: \begin{align} \vec{f}_m^e &= \int\limits_{\Omega^e} \big[\, \vec{\nabla} N_m \,\big] \cdot \bm{\sigma}(\vec{x}) \; \mathrm{d}\Omega = \int\limits_{Q} \big[\, \vec{\nabla} N_m \,\big] \cdot \bm{\sigma}(\vec{x}) \; \det \big( \bm{J}(\vec{\xi}) \big) \; \mathrm{d}Q \\ &= \sum_{k}^{n_k} w_k \big[\, \vec{\nabla} N_m \,\big]_{\vec{\xi} = \vec{\xi}_k} \cdot \bm{\sigma}\big(\vec{x}(\vec{\xi}_k)\big) \; \det \big( \bm{J}(\vec{\xi}_k) \big) \; \end{align} \subsection*{Total Lagrange} To obtain \begin{equation} - \vec{x}(\vec{\xi}),\, \vec{\nabla}_0,\,\, \text{and}\, \int\limits_{\Omega_0} . \;\mathrm{d}\Omega + \vec{x}(\vec{\xi}),\, \vec{\nabla}_0,\,\, \text{and}\, \int\limits_{\Omega_0} (\ldots) \;\mathrm{d}\Omega \end{equation} simply replace $\vec{x}_m^e$ with $\vec{X}_m^e$ in Eq.~\eqref{eq:isoparametric:coordinate}. For this reason the same element implementation can be used in small strain and finite strain (total Lagrange and updated Lagrange), by providing either $\vec{X}_m^e$ or $\vec{X}_m^e + \vec{u}_m^e$ as input. \section{Newton-Raphson in one dimension} \label{sec:newton-raphson} The objective is to find $x$ such that \begin{equation} r(x) = 0 \end{equation} An initial guess is made for $x$ which is (hopefully) iteratively improved. This iterative value is denoted using $x_{(i)}$. It is updated by making use of the following Taylor expansion \begin{equation} r \big( x_{(i+1)} \big) = r \big( x_{(i)} \big) + \left. \frac{\mathrm{d} r}{\mathrm{d} x} \right|_{x = x_{(i)}} \delta x + \mathcal{O} \big( \delta x^2 \big) \approx 0 \end{equation} where \begin{equation} \delta x = x_{(i+1)} - x_{(i)} \end{equation} The value of $\delta x$ can be obtained by neglecting higher order terms: \begin{equation} r \big( x_{(i)} \big) + \left. \frac{\mathrm{d} r}{\mathrm{d} x} \right|_{x = x_{(i)}} \delta x = 0 \end{equation} From which it follows that \begin{equation} \delta x = - \left[ \left. \frac{\mathrm{d} r}{\mathrm{d} x} \right|_{x = x_{(i)}} \right]^{-1} r \big( x_{(i)} \big) \end{equation} Thereafter, the improved `guess' of the solution is \begin{equation} x_{(i+1)} = x_{(i)} + \delta x \end{equation} These steps are then repeated, until the solution has been reached within a certain accuracy $\epsilon$: \begin{equation} \left| r \big( x_{(i+1)} \big) \right| < \epsilon \end{equation} - The iterative scheme is well understood from the illustration in Fig.~\ref{fig:newton-raphson}. \begin{figure}[htp] \centering - \includegraphics[width=.3\textwidth]{figures/newton-raphson.pdf} + \includegraphics[width=.4\textwidth]{figures/newton-raphson.pdf} \caption{Schematic of the Newton-Raphson iterations in a one-dimensional problem.} \label{fig:newton-raphson} \end{figure} \section{B-matrix} \subsection{Discretisation} -The discretisation from Eq.~\eqref{eq:discretization} can be written more precisely as +The discretisation from Eq.~\eqref{eq:discretisation} can be written more precisely as \begin{equation} \phi_x(\vec{x}) \vec{e}_x + \phi_y(\vec{x}) \vec{e}_y + \phi_z(\vec{x}) \vec{e}_z \approx \sum_{m=1}^{n_\mathrm{nodes}} N_m (\vec{x}) \; \phi_{m,x} \vec{e}_x + N_m (\vec{x}) \; \phi_{m,y} \vec{e}_y + N_m (\vec{x}) \; \phi_{m,z} \vec{e}_z \end{equation} whereby $\vec{\phi}(\vec{x})$ is a vector field such as the test functions, the displacement, the forces, etc. The resulting shape function gradients then read \begin{align} & \vec{\nabla} \vec{\phi}(\vec{x}) \approx \sum_{m=1}^{n_\mathrm{nodes}} \bigg( \vec{e}_x \frac{\partial}{\partial x} + \vec{e}_y \frac{\partial}{\partial y} + \vec{e}_z \frac{\partial}{\partial z} \bigg) \bigg( N_m (\vec{x}) \; \phi_{m,x} \vec{e}_x + N_m (\vec{x}) \; \phi_{m,y} \vec{e}_y + N_m (\vec{x}) \; \phi_{m,z} \vec{e}_z \bigg) \\ & = \phi_{m,x} \frac{\partial N_m}{\partial x} \vec{e}_x \vec{e}_x + \nonumber \phi_{m,y} \frac{\partial N_m}{\partial x} \vec{e}_x \vec{e}_y + \nonumber \phi_{m,z} \frac{\partial N_m}{\partial x} \vec{e}_x \vec{e}_z \\ & + \phi_{m,x} \frac{\partial N_m}{\partial y} \vec{e}_y \vec{e}_x + \phi_{m,y} \frac{\partial N_m}{\partial y} \vec{e}_y \vec{e}_y + \phi_{m,z} \frac{\partial N_m}{\partial y} \vec{e}_y \vec{e}_z \\ & + \phi_{m,x} \frac{\partial N_m}{\partial z} \vec{e}_z \vec{e}_x + \phi_{m,y} \frac{\partial N_m}{\partial z} \vec{e}_z \vec{e}_y + \phi_{m,z} \frac{\partial N_m}{\partial z} \vec{e}_z \vec{e}_z \end{align} Which can denote in short as \begin{equation} \label{eq:bmatrix:discretisation} \vec{\nabla} \vec{\phi} (\vec{x}) \approx \sum_{m=1}^{n_\mathrm{nodes}} \mathcal{B}_m (\vec{x}) \cdot \vec{\phi}_m \end{equation} whereby $\mathcal{B}_m$ is third order tensor that collects the gradient of the shape function of a node $m$. The only non-zero components are \begin{alignat}{3} \mathcal{B}_{m,x x x} &= \frac{\partial N_m}{\partial x} & \qquad \mathcal{B}_{m,y x x} &= \frac{\partial N_m}{\partial y} & \qquad \mathcal{B}_{m,z x x} &= \frac{\partial N_m}{\partial z} \nonumber \\ \mathcal{B}_{m,x y y} &= \frac{\partial N_m}{\partial x} & \qquad \mathcal{B}_{m,y y y} &= \frac{\partial N_m}{\partial y} & \qquad \mathcal{B}_{m,z y y} &= \frac{\partial N_m}{\partial z} \\ \mathcal{B}_{m,x z z} &= \frac{\partial N_m}{\partial x} & \qquad \mathcal{B}_{m,y z z} &= \frac{\partial N_m}{\partial y} & \qquad \mathcal{B}_{m,z z z} &= \frac{\partial N_m}{\partial z} \end{alignat} \subsection{Applied to the weak form} By means of example the organisation using the `B-matrix' can be applied to the weak form of Eq.~\eqref{eq:static:weak:final}, which reads \begin{equation} \int\limits_\Omega \big[\, \vec{\nabla} \vec{\phi}(\vec{x}) \,\big] : \bm{\sigma}(\vec{x}) \; \mathrm{d}\Omega = \int\limits_\Gamma \vec{\phi}(\vec{x}) \cdot \vec{t}(\vec{x}) \; \mathrm{d}\Gamma \qquad \forall \; \vec{\phi}(\vec{x}) \in \mathbb{R}^d \end{equation} Substituting Eq.~\eqref{eq:bmatrix:discretisation} results in \begin{equation} \int\limits_{\Omega^h} \left( \mathcal{B}_m \cdot \vec{\phi}_m \right) : \bm{\sigma}(\vec{x}) \; \mathrm{d}\Omega = \int\limits_{\Gamma^h} N_m (\vec{x}) \; \vec{\phi}_m \cdot \vec{t}(\vec{x}) \; \mathrm{d}\Gamma \qquad \forall \; \vec{\phi}_m \in \mathbb{R}^d_n \end{equation} from which we can take the nodal values of the test functions $\vec{\phi}_m$, that no longer depend on space, out of the volume integral: \begin{equation} \vec{\phi}_m \cdot \int\limits_{\Omega^h} \mathcal{B}_m^T : \bm{\sigma}(\vec{x}) \; \mathrm{d}\Omega = \vec{\phi}_m \cdot \int\limits_{\Gamma^h} N_m(\vec{x}) \; \vec{t}(\vec{x}) \; \mathrm{d}\Gamma \qquad \forall \; \vec{\phi}_m \in \mathbb{R}^d_n \end{equation} From which it is obvious that the dependence on $\vec{\phi}_m$ can be dropped, resulting in the discretised weak form: \begin{equation} \label{eq:bmatrix:weak_form:discrete} \int\limits_{\Omega^h} \mathcal{B}_m^T : \bm{\sigma}(\vec{x}) \; \mathrm{d}\Omega = \int\limits_{\Gamma^h} N_m(\vec{x}) \; \vec{t}(\vec{x}) \; \mathrm{d}\Gamma \end{equation} \subsection{Applied to linear elasticity} As a further illustration linear elasticity can be assumed. Application to Eq.~\eqref{eq:bmatrix:weak_form:discrete} trivially results in \begin{equation} \int\limits_{\Omega_0^h} \tilde{\mathcal{B}}_m^T : \Big( \mathbb{C} : \bm{\varepsilon}(\vec{x}) \Big) \; \mathrm{d}\Omega = \int\limits_{\Gamma^h} N_m(\vec{x}) \; \vec{t}(\vec{x}) \; \mathrm{d}\Gamma \end{equation} where $\tilde{\cdot}$ emphasises that $\tilde{\mathcal{B}}_m$ are the gradients of the shape functions with respect to the coordinates in the, undeformed, reference frame. Now, because $\mathbb{C}$ symmetrises, this expression can be written in terms of the displacement gradient directly: \begin{equation} \int\limits_{\Omega_0^h} \tilde{\mathcal{B}}_m^T : \Big( \mathbb{C} : \vec{\nabla}\vec{u}(\vec{x}) \Big) \; \mathrm{d}\Omega = \int\limits_{\Gamma^h} N_m(\vec{x}) \; \vec{t}(\vec{x}) \; \mathrm{d}\Gamma \end{equation} The displacement gradient is evaluated discretely using Eq.~\eqref{eq:bmatrix:weak_form:discrete}, resulting in: \begin{equation} \int\limits_{\Omega_0^h} \tilde{\mathcal{B}}_m^T : \Big( \mathbb{C} : ( \tilde{\mathcal{B}}_n \cdot \vec{u}_n) \Big) \; \mathrm{d}\Omega = \int\limits_{\Gamma^h} N_m(\vec{x}) \; \vec{t}(\vec{x}) \; \mathrm{d}\Gamma \end{equation} whereby, again, the nodal displacements, $\vec{u}_n$, are not a function of the position and can be taken out of the integral, such as to finally have a system that can be solved: \begin{equation} \int\limits_{\Omega_0^h} \tilde{\mathcal{B}}_m^T : \Big( \mathbb{C} : \tilde{\mathcal{B}}_n \Big) \; \mathrm{d}\Omega \cdot \vec{u}_n = \int\limits_{\Gamma^h} N_m(\vec{x}) \; \vec{t}(\vec{x}) \; \mathrm{d}\Gamma \end{equation} \section{Cylindrical coordinates} -\begin{figure}[htp] - \centering - \includegraphics[width=.3\textwidth]{figures/cylindrical_coordinates} - \caption{Cylindrical coordinate system spanned by $(\vec{e}_r, \vec{e}_\theta, \vec{e}_z)$.} - \label{fig:cylindrical} -\end{figure} +\paragraph{Coordinates} The cylindrical coordinate system is spanned by $(\vec{e}_r, \vec{e}_\theta, \vec{e}_z)$ as visualised in Fig.~\ref{fig:cylindrical}. From this figure, the transition from cylindrical coordinates to Cartesian coordinates trivially follows to read \begin{equation} x = r \cos \theta \qquad y = r \sin \theta \qquad z = z \end{equation} and the inverse relation to read \begin{equation} r = \sqrt{ x^2 + y^2 } \qquad \theta = \mathrm{arctan}\left( y/x \right) \qquad z = z \end{equation} Likewise, the unit vectors $(\vec{e}_r, \vec{e}_\theta, \vec{e}_z)$ can be expressed in terms of the unit vectors that span the Cartesian basis: \begin{align} \vec{e}_r &= \cos \theta \; \vec{e}_x + \sin \theta \; \vec{e}_y \nonumber \\ \vec{e}_\theta &= - \sin \theta \; \vec{e}_x + \cos \theta \; \vec{e}_y \nonumber \\ \vec{e}_z &= \vec{e}_z \label{eq:cylindrical:unit} \end{align} - The gradient operator for cylindrical coordinates reads \begin{equation} \vec{\nabla} = \vec{e}_r \frac{\partial}{\partial r} + \frac{1}{r} \vec{e}_\theta \frac{\partial}{\partial \theta} + \vec{e}_z \frac{\partial}{\partial z} \end{equation} whereby from Eq.~\eqref{eq:cylindrical:unit} the derivates of the unit vector follow to be \begin{alignat}{3} \frac{\partial \vec{e}_r}{\partial r } &= 0 &\qquad \frac{\partial \vec{e}_\theta}{\partial r } &= 0 &\qquad \frac{\partial \vec{e}_z}{\partial r } &= 0 \nonumber \\ \frac{\partial \vec{e}_r}{\partial \theta} &= \vec{e}_\theta &\qquad \frac{\partial \vec{e}_\theta}{\partial \theta} &= - \vec{e}_r &\qquad \frac{\partial \vec{e}_z}{\partial \theta} &= 0 \nonumber \\ \frac{\partial \vec{e}_r}{\partial z } &= 0 &\qquad \frac{\partial \vec{e}_\theta}{\partial z } &= 0 &\qquad \frac{\partial \vec{e}_z}{\partial z } &= 0 \end{alignat} +\begin{figure}[htp] + \centering + \includegraphics[width=.3\textwidth]{figures/cylindrical_coordinates} + \caption{Cylindrical coordinate system spanned by $(\vec{e}_r, \vec{e}_\theta, \vec{e}_z)$.} + \label{fig:cylindrical} +\end{figure} + +\paragraph{Discretisation} + A discretised vector field $\vec{\phi}(\vec{x})$ reads \begin{equation} \phi_r (\vec{x}) \vec{e}_r + \phi_\theta (\vec{x}) \vec{e}_\theta + \phi_z (\vec{x}) \vec{e}_z \approx \sum\limits_{m=1}^{n_\mathrm{nodes}} N^m (\vec{x}) \phi^{m}_{r} \vec{e}_r + N^m (\vec{x}) \phi^{m}_{\theta} \vec{e}_\theta + N^m (\vec{x}) \phi^{m}_{z} \vec{e}_z \end{equation} And consequently its discretised gradient reads \begin{align} &\vec{\nabla} \vec{\phi} (\vec{x}) \approx \sum\limits_{m=1}^{n_\mathrm{nodes}} \Bigg[ \bigg( \vec{e}_r \frac{\partial}{\partial r} + \frac{1}{r} \vec{e}_\theta \frac{\partial}{\partial \theta} + \vec{e}_z \frac{\partial}{\partial z} \bigg) \bigg( - N^m (\vec{x}) \phi^{m}_{r} \vec{e}_r + - N^m (\vec{x}) \phi^{m}_{\theta} \vec{e}_\theta + - N^m (\vec{x}) \phi^{m}_{z} \vec{e}_z + N^m \phi^{m}_{r} \vec{e}_r + + N^m \phi^{m}_{\theta} \vec{e}_\theta + + N^m \phi^{m}_{z} \vec{e}_z \bigg) \Bigg] \\ &= \sum\limits_{m=1}^{n_\mathrm{nodes}} \Bigg[ \phi^{m}_{r } \frac{\partial N^m}{\partial r} \vec{e}_r \vec{e}_r + \phi^{m}_{\theta} \frac{\partial N^m}{\partial r} \vec{e}_r \vec{e}_\theta + \phi^{m}_{z } \frac{\partial N^m}{\partial r} \vec{e}_r \vec{e}_z \nonumber \\ &\quad - + \phi^{m}_{r } \frac{1}{r} \frac{\partial N^m }{\partial \theta} \vec{e}_\theta \vec{e}_r - + \phi^{m}_{r } \frac{1}{r} N^m (\vec{x}) \vec{e}_\theta \vec{e}_\theta - + \phi^{m}_{\theta} \frac{1}{r} \frac{\partial N^m }{\partial \theta} \vec{e}_\theta \vec{e}_\theta - - \phi^{m}_{\theta} \frac{1}{r} N^m (\vec{x}) \vec{e}_r \vec{e}_\theta - + \phi^{m}_{z } \frac{1}{r} \frac{\partial N^m }{\partial \theta} \vec{e}_\theta \vec{e}_z + + \phi^{m}_{r } \frac{1}{r} \frac{\partial N^m}{\partial \theta} \vec{e}_\theta \vec{e}_r + + \phi^{m}_{r } \frac{1}{r} N^m \vec{e}_\theta \vec{e}_\theta + + \phi^{m}_{\theta} \frac{1}{r} \frac{\partial N^m}{\partial \theta} \vec{e}_\theta \vec{e}_\theta + - \phi^{m}_{\theta} \frac{1}{r} N^m \vec{e}_r \vec{e}_\theta + + \phi^{m}_{z } \frac{1}{r} \frac{\partial N^m}{\partial \theta} \vec{e}_\theta \vec{e}_z \nonumber \\ &\quad + \phi^{m}_{r } \frac{\partial N^m }{\partial z} \vec{e}_z \vec{e}_r + \phi^{m}_{\theta} \frac{\partial N^m }{\partial z} \vec{e}_z \vec{e}_\theta + \phi^{m}_{z } \frac{\partial N^m }{\partial z} \vec{e}_z \vec{e}_z \Bigg] \end{align} -Which can be denoted in short as +whereby the spatial dependence was not denoted, but naturally persists. The above can be denoted in short as \begin{equation} \vec{\nabla} \vec{\phi} (\vec{x}) \approx \sum\limits_{m=1}^{n_\mathrm{nodes}} \mathcal{B}_m (\vec{x}) \cdot \vec{\phi}_m \end{equation} where the non-zero components of the third order $\mathcal{B}_m$ read -\begin{alignat}{3} - \mathcal{B}^{m}_{r r r } &= \frac{\partial N^m}{\partial r} & \qquad - \mathcal{B}^{m}_{\theta r r } &= \frac{1}{r} \frac{\partial N^m}{\partial \theta} & \qquad - \mathcal{B}^{m}_{z r r } &= \frac{\partial N^m}{\partial z} +\begin{alignat}{4} + \mathcal{B}^{m}_{r r r } &= \frac{\partial N^m}{\partial r} & \qquad + \mathcal{B}^{m}_{r \theta \theta} &= \frac{\partial N^m}{\partial r} - \frac{1}{r} N^m & \qquad + \mathcal{B}^{m}_{r z z } &= \frac{\partial N^m}{\partial r} \nonumber \\ - \mathcal{B}^{m}_{r \theta \theta} &= - \frac{1}{r} N^m (\vec{x}) & \qquad - \mathcal{B}^{m}_{\theta \theta r } &= \frac{1}{r} N^m (\vec{x}) & \qquad + \mathcal{B}^{m}_{\theta r r } &= \frac{1}{r} \frac{\partial N^m}{\partial \theta} & \qquad + \mathcal{B}^{m}_{\theta \theta \theta} &= \frac{1}{r} \frac{\partial N^m}{\partial \theta} & \qquad + \mathcal{B}^{m}_{\theta z z } &= \frac{1}{r} \frac{\partial N^m}{\partial \theta} & \qquad + \mathcal{B}^{m}_{\theta \theta r } &= \frac{1}{r} N^m \nonumber \\ - \mathcal{B}^{m}_{r \theta \theta} &= \frac{\partial N^m}{\partial r} & \qquad - \mathcal{B}^{m}_{\theta \theta \theta} &= \frac{1}{r} \frac{\partial N^m}{\partial \theta} & \qquad - \mathcal{B}^{m}_{z \theta \theta} &= \frac{\partial N^m}{\partial z} - \\ - \mathcal{B}^{m}_{r z z } &= \frac{\partial N^m}{\partial r} & \qquad - \mathcal{B}^{m}_{\theta z z } &= \frac{1}{r} \frac{\partial N^m}{\partial \theta} & \qquad - \mathcal{B}^{m}_{z z z } &= \frac{\partial N^m}{\partial z} + \mathcal{B}^{m}_{z r r } &= \frac{\partial N^m}{\partial z} & \qquad + \mathcal{B}^{m}_{z \theta \theta} &= \frac{\partial N^m}{\partial z} & \qquad + \mathcal{B}^{m}_{z z z } &= \frac{\partial N^m}{\partial z} \end{alignat} -Finally a Jacobian $\bm{J}$ can be defined to change coordinates as follows +\paragraph{Jacobian} + +A Jacobian $\bm{J}$ can be defined to change coordinates as follows \begin{equation} \vec{\nabla}_\xi = \bm{J} \cdot \vec{\nabla} \end{equation} which in components reads \begin{equation} \vec{e}_\xi \frac{\partial}{\partial \xi } + \vec{e}_\eta \frac{\partial}{\partial \eta} + \vec{e}_\mu \frac{\partial}{\partial \mu} = \bm{J} \cdot \left( \vec{e}_r \frac{\partial}{\partial r } + \frac{1}{r} \vec{e}_\theta \frac{\partial}{\partial \theta} + \vec{e}_z \frac{\partial}{\partial z } \right) \end{equation} The Jacobian is then obtained as \begin{align} \bm{J} &= \vec{e}_\xi \frac{\partial r }{\partial \xi } \vec{e}_r + \vec{e}_\xi r \frac{\partial \theta}{\partial \xi } \vec{e}_\theta + \vec{e}_\xi \frac{\partial z }{\partial \xi } \vec{e}_z \nonumber \\ &+ \vec{e}_\eta \frac{\partial r }{\partial \eta} \vec{e}_r + \vec{e}_\eta r \frac{\partial \theta}{\partial \eta} \vec{e}_\theta + \vec{e}_\eta \frac{\partial z }{\partial \eta} \vec{e}_z \nonumber \\ &+ \vec{e}_\mu \frac{\partial r }{\partial \mu } \vec{e}_r + \vec{e}_\mu r \frac{\partial \theta}{\partial \mu } \vec{e}_\theta + \vec{e}_\mu \frac{\partial z }{\partial \mu } \vec{e}_z \end{align} +\paragraph{Axisymmetric} + +Assuming axisymmetry implies assuming that there can be no variations along the $\vec{e}_\theta$ direction. This implies that any derivate $\partial / \partial \theta = 0$ and consequently that there are no degrees-of-freedom in the $\theta$ direction, i.e.\ $u_\theta = 0$. This implies that the only relevant non-zero components of the third order $\mathcal{B}_m$ read +\begin{alignat}{4} + \mathcal{B}^{m}_{r r r } &= \frac{\partial N^m}{\partial r} & \qquad + \mathcal{B}^{m}_{r z z } &= \frac{\partial N^m}{\partial r} + \nonumber + \\ + \mathcal{B}^{m}_{\theta \theta r } &= \frac{1}{r} N^m + \nonumber + \\ + \mathcal{B}^{m}_{z r r } &= \frac{\partial N^m}{\partial z} & \qquad + \mathcal{B}^{m}_{z z z } &= \frac{\partial N^m}{\partial z} +\end{alignat} +The Jacobian is furthermore reduced to +\begin{align} + \bm{J} + &= + \vec{e}_\xi \frac{\partial r }{\partial \xi } \vec{e}_r + + \vec{e}_\xi \frac{\partial z }{\partial \xi } \vec{e}_z + \nonumber + \\ + &+ + \vec{e}_\eta \frac{\partial r }{\partial \eta} \vec{e}_r + + \vec{e}_\eta \frac{\partial z }{\partial \eta} \vec{e}_z +\end{align} % \bibliography{example_refs} \end{document} diff --git a/docs/theory/symbolic/axisymmetric.py b/docs/theory/symbolic/axisymmetric.py new file mode 100644 index 0000000..f5f6a3b --- /dev/null +++ b/docs/theory/symbolic/axisymmetric.py @@ -0,0 +1,42 @@ + +import numpy as np + +B = np.zeros((3,3,3)) +BT = np.zeros((3,3,3)) + +B[0,1,1] = 1. # B(r \theta \theta ) +B[1,1,0] = 1. # B(\theta \theta r ) +B[0,0,0] = 1. # B(r r r ) +B[0,1,1] = 1. # B(r \theta \theta ) +B[0,2,2] = 1. # B(r z z ) +B[2,0,0] = 1. # B(z r r ) +B[2,1,1] = 1. # B(z \theta \theta ) +B[2,2,2] = 1. # B(z z z ) + +for i in range(3): + for j in range(3): + for k in range(3): + BT[k,j,i] = B[i,j,k] + +C = np.ones((3,3,3,3)) + +K = [['' for j in range(3)] for i in range(3)] + +def st(i): + if i == 0: return 'r' + if i == 1: return 't' + if i == 2: return 'z' + +for i in range(3): + for j in range(3): + for k in range(3): + for l in range(3): + for m in range(3): + for n in range(3): + if BT[i,j,k] * C[k,j,l,m] * B[m,l,n]: + K[i][n] += ' + ' + 'B(%s,%s,%s)'%(st(k),st(j),st(i)) + '*C(%s,%s,%s,%s)*'%(st(k),st(j),st(l),st(m)) + 'B(%s,%s,%s)'%(st(m),st(l),st(n)) + +i = 0; j = 0; print(i,j,K[i][j]) +i = 0; j = 2; print(i,j,K[i][j]) +i = 2; j = 0; print(i,j,K[i][j]) +i = 2; j = 2; print(i,j,K[i][j]) diff --git a/docs/theory/unsrtnat.bst b/docs/theory/unsrtnat.bst index 1c7eddf..a212b66 100644 --- a/docs/theory/unsrtnat.bst +++ b/docs/theory/unsrtnat.bst @@ -1,1343 +1,1343 @@ %% File: `unsrtnat.bst' -%% A modification of `unsrt.bst' for use with natbib package +%% A modification of `unsrt.bst' for use with natbib package %% %% Copyright 1993-2007 Patrick W Daly %% Max-Planck-Institut f\"ur Sonnensystemforschung %% Max-Planck-Str. 2 %% D-37191 Katlenburg-Lindau %% Germany %% E-mail: daly@mps.mpg.de %% %% This program can be redistributed and/or modified under the terms %% of the LaTeX Project Public License Distributed from CTAN %% archives in directory macros/latex/base/lppl.txt; either %% version 1 of the License, or any later version. %% % Version and source file information: % \ProvidesFile{natbst.mbs}[2007/11/26 1.93 (PWD)] % % BibTeX `plainnat' family % version 0.99b for BibTeX versions 0.99a or later, % for LaTeX versions 2.09 and 2e. % % For use with the `natbib.sty' package; emulates the corresponding % member of the `plain' family, but with author-year citations. % % With version 6.0 of `natbib.sty', it may also be used for numerical % citations, while retaining the commands \citeauthor, \citefullauthor, % and \citeyear to print the corresponding information. % % For version 7.0 of `natbib.sty', the KEY field replaces missing % authors/editors, and the date is left blank in \bibitem. % % Includes field EID for the sequence/citation number of electronic journals % which is used instead of page numbers. % % Includes fields ISBN and ISSN. % % Includes field URL for Internet addresses. % % Includes field DOI for Digital Object Idenfifiers. % % Works best with the url.sty package of Donald Arseneau. % % Works with identical authors and year are further sorted by % title text, as in the standard plain.bst etc. % ENTRY { address author booktitle chapter doi - eprint + arxivid eid edition editor howpublished institution isbn issn journal key month note number organization pages publisher school series title type url volume year } {} { label extra.label sort.label short.list } INTEGERS { output.state before.all mid.sentence after.sentence after.block } FUNCTION {init.state.consts} { #0 'before.all := #1 'mid.sentence := #2 'after.sentence := #3 'after.block := } STRINGS { s t } FUNCTION {output.nonnull} { 's := output.state mid.sentence = { ", " * write$ } { output.state after.block = { add.period$ write$ newline$ "\newblock " write$ } { output.state before.all = 'write$ { add.period$ " " * write$ } if$ } if$ mid.sentence 'output.state := } if$ s } FUNCTION {output} { duplicate$ empty$ 'pop$ 'output.nonnull if$ } FUNCTION {output.check} { 't := duplicate$ empty$ { pop$ "empty " t * " in " * cite$ * warning$ } 'output.nonnull if$ } FUNCTION {fin.entry} { add.period$ write$ newline$ } FUNCTION {new.block} { output.state before.all = 'skip$ { after.block 'output.state := } if$ } FUNCTION {new.sentence} { output.state after.block = 'skip$ { output.state before.all = 'skip$ { after.sentence 'output.state := } if$ } if$ } FUNCTION {not} { { #0 } { #1 } if$ } FUNCTION {and} { 'skip$ { pop$ #0 } if$ } FUNCTION {or} { { pop$ #1 } 'skip$ if$ } FUNCTION {new.block.checka} { empty$ 'skip$ 'new.block if$ } FUNCTION {new.block.checkb} { empty$ swap$ empty$ and 'skip$ 'new.block if$ } FUNCTION {new.sentence.checka} { empty$ 'skip$ 'new.sentence if$ } FUNCTION {new.sentence.checkb} { empty$ swap$ empty$ and 'skip$ 'new.sentence if$ } FUNCTION {field.or.null} { duplicate$ empty$ { pop$ "" } 'skip$ if$ } FUNCTION {emphasize} { duplicate$ empty$ { pop$ "" } { "\emph{" swap$ * "}" * } if$ } INTEGERS { nameptr namesleft numnames } FUNCTION {format.names} { 's := #1 'nameptr := s num.names$ 'numnames := numnames 'namesleft := { namesleft #0 > } { s nameptr "{ff~}{vv~}{ll}{, jj}" format.name$ 't := nameptr #1 > { namesleft #1 > { ", " * t * } { numnames #2 > { "," * } 'skip$ if$ t "others" = { " et~al." * } { " and " * t * } if$ } if$ } 't if$ nameptr #1 + 'nameptr := namesleft #1 - 'namesleft := } while$ } FUNCTION {format.key} { empty$ { key field.or.null } { "" } if$ } FUNCTION {format.authors} { author empty$ { "" } { author format.names } if$ } FUNCTION {format.editors} { editor empty$ { "" } { editor format.names editor num.names$ #1 > { ", editors" * } { ", editor" * } if$ } if$ } FUNCTION {format.isbn} { isbn empty$ { "" } { new.block "ISBN " isbn * } if$ } FUNCTION {format.issn} { issn empty$ { "" } { new.block "ISSN " issn * } if$ } FUNCTION {format.url} { url empty$ { "" } { new.block "URL \url{" url * "}" * } if$ } FUNCTION {format.doi} { doi empty$ { "" } { new.block "\doi{" doi * "}" * } if$ } -FUNCTION {format.eprint} -{ eprint empty$ +FUNCTION {format.arxivid} +{ arxivid empty$ { "" } - { new.block "\eprint{" eprint * "}" * } + { new.block "\arxivid{" arxivid * "}" * } if$ } FUNCTION {format.title} { title empty$ { "" } { title "t" change.case$ } if$ } FUNCTION {format.full.names} {'s := #1 'nameptr := s num.names$ 'numnames := numnames 'namesleft := { namesleft #0 > } { s nameptr "{vv~}{ll}" format.name$ 't := nameptr #1 > { namesleft #1 > { ", " * t * } { numnames #2 > { "," * } 'skip$ if$ t "others" = { " et~al." * } { " and " * t * } if$ } if$ } 't if$ nameptr #1 + 'nameptr := namesleft #1 - 'namesleft := } while$ } FUNCTION {author.editor.full} { author empty$ { editor empty$ { "" } { editor format.full.names } if$ } { author format.full.names } if$ } FUNCTION {author.full} { author empty$ { "" } { author format.full.names } if$ } FUNCTION {editor.full} { editor empty$ { "" } { editor format.full.names } if$ } FUNCTION {make.full.names} { type$ "book" = type$ "inbook" = or 'author.editor.full { type$ "proceedings" = 'editor.full 'author.full if$ } if$ } FUNCTION {output.bibitem} { newline$ "\bibitem[" write$ label write$ ")" make.full.names duplicate$ short.list = { pop$ } { * } if$ "]{" * write$ cite$ write$ "}" write$ newline$ "" before.all 'output.state := } FUNCTION {n.dashify} { 't := "" { t empty$ not } { t #1 #1 substring$ "-" = { t #1 #2 substring$ "--" = not { "--" * t #2 global.max$ substring$ 't := } { { t #1 #1 substring$ "-" = } { "-" * t #2 global.max$ substring$ 't := } while$ } if$ } { t #1 #1 substring$ * t #2 global.max$ substring$ 't := } if$ } while$ } FUNCTION {format.date} { year duplicate$ empty$ { "empty year in " cite$ * warning$ pop$ "" } 'skip$ if$ month empty$ 'skip$ { month " " * swap$ * } if$ extra.label * } FUNCTION {format.btitle} { title emphasize } FUNCTION {tie.or.space.connect} { duplicate$ text.length$ #3 < { "~" } { " " } if$ swap$ * * } FUNCTION {either.or.check} { empty$ 'pop$ { "can't use both " swap$ * " fields in " * cite$ * warning$ } if$ } FUNCTION {format.bvolume} { volume empty$ { "" } { "volume" volume tie.or.space.connect series empty$ 'skip$ { " of " * series emphasize * } if$ "volume and number" number either.or.check } if$ } FUNCTION {format.number.series} { volume empty$ { number empty$ { series field.or.null } { output.state mid.sentence = { "number" } { "Number" } if$ number tie.or.space.connect series empty$ { "there's a number but no series in " cite$ * warning$ } { " in " * series * } if$ } if$ } { "" } if$ } FUNCTION {format.edition} { edition empty$ { "" } { output.state mid.sentence = { edition "l" change.case$ " edition" * } { edition "t" change.case$ " edition" * } if$ } if$ } INTEGERS { multiresult } FUNCTION {multi.page.check} { 't := #0 'multiresult := { multiresult not t empty$ not and } { t #1 #1 substring$ duplicate$ "-" = swap$ duplicate$ "," = swap$ "+" = or or { #1 'multiresult := } { t #2 global.max$ substring$ 't := } if$ } while$ multiresult } FUNCTION {format.pages} { pages empty$ { "" } { pages multi.page.check { "pages" pages n.dashify tie.or.space.connect } { "page" pages tie.or.space.connect } if$ } if$ } FUNCTION {format.eid} { eid empty$ { "" } { "art." eid tie.or.space.connect } if$ } FUNCTION {format.vol.num.pages} { volume field.or.null number empty$ 'skip$ { "\penalty0 (" number * ")" * * volume empty$ { "there's a number but no volume in " cite$ * warning$ } 'skip$ if$ } if$ pages empty$ 'skip$ { duplicate$ empty$ { pop$ format.pages } { ":\penalty0 " * pages n.dashify * } if$ } if$ } FUNCTION {format.vol.num.eid} { volume field.or.null number empty$ 'skip$ { "\penalty0 (" number * ")" * * volume empty$ { "there's a number but no volume in " cite$ * warning$ } 'skip$ if$ } if$ eid empty$ 'skip$ { duplicate$ empty$ { pop$ format.eid } { ":\penalty0 " * eid * } if$ } if$ } FUNCTION {format.chapter.pages} { chapter empty$ 'format.pages { type empty$ { "chapter" } { type "l" change.case$ } if$ chapter tie.or.space.connect pages empty$ 'skip$ { ", " * format.pages * } if$ } if$ } FUNCTION {format.in.ed.booktitle} { booktitle empty$ { "" } { editor empty$ { "In " booktitle emphasize * } { "In " format.editors * ", " * booktitle emphasize * } if$ } if$ } FUNCTION {empty.misc.check} { author empty$ title empty$ howpublished empty$ month empty$ year empty$ note empty$ and and and and and key empty$ not and { "all relevant fields are empty in " cite$ * warning$ } 'skip$ if$ } FUNCTION {format.thesis.type} { type empty$ 'skip$ { pop$ type "t" change.case$ } if$ } FUNCTION {format.tr.number} { type empty$ { "Technical Report" } 'type if$ number empty$ { "t" change.case$ } { number tie.or.space.connect } if$ } FUNCTION {format.article.crossref} { key empty$ { journal empty$ { "need key or journal for " cite$ * " to crossref " * crossref * warning$ "" } { "In \emph{" journal * "}" * } if$ } { "In " } if$ " \citet{" * crossref * "}" * } FUNCTION {format.book.crossref} { volume empty$ { "empty volume in " cite$ * "'s crossref of " * crossref * warning$ "In " } { "Volume" volume tie.or.space.connect " of " * } if$ editor empty$ editor field.or.null author field.or.null = or { key empty$ { series empty$ { "need editor, key, or series for " cite$ * " to crossref " * crossref * warning$ "" * } { "\emph{" * series * "}" * } if$ } 'skip$ if$ } 'skip$ if$ " \citet{" * crossref * "}" * } FUNCTION {format.incoll.inproc.crossref} { editor empty$ editor field.or.null author field.or.null = or { key empty$ { booktitle empty$ { "need editor, key, or booktitle for " cite$ * " to crossref " * crossref * warning$ "" } { "In \emph{" booktitle * "}" * } if$ } { "In " } if$ } { "In " } if$ " \citet{" * crossref * "}" * } FUNCTION {article} { output.bibitem format.authors "author" output.check author format.key output new.block format.title "title" output.check new.block crossref missing$ { journal emphasize "journal" output.check eid empty$ { format.vol.num.pages output } { format.vol.num.eid output } if$ format.date "year" output.check } { format.article.crossref output.nonnull eid empty$ { format.pages output } { format.eid output } if$ } if$ format.issn output format.doi output - format.eprint output + format.arxivid output format.url output new.block note output fin.entry } FUNCTION {book} { output.bibitem author empty$ { format.editors "author and editor" output.check editor format.key output } { format.authors output.nonnull crossref missing$ { "author and editor" editor either.or.check } 'skip$ if$ } if$ new.block format.btitle "title" output.check crossref missing$ { format.bvolume output new.block format.number.series output new.sentence publisher "publisher" output.check address output } { new.block format.book.crossref output.nonnull } if$ format.edition output format.date "year" output.check format.isbn output format.doi output - format.eprint output + format.arxivid output format.url output new.block note output fin.entry } FUNCTION {booklet} { output.bibitem format.authors output author format.key output new.block format.title "title" output.check howpublished address new.block.checkb howpublished output address output format.date output format.isbn output format.doi output - format.eprint output + format.arxivid output format.url output new.block note output fin.entry } FUNCTION {inbook} { output.bibitem author empty$ { format.editors "author and editor" output.check editor format.key output } { format.authors output.nonnull crossref missing$ { "author and editor" editor either.or.check } 'skip$ if$ } if$ new.block format.btitle "title" output.check crossref missing$ { format.bvolume output format.chapter.pages "chapter and pages" output.check new.block format.number.series output new.sentence publisher "publisher" output.check address output } { format.chapter.pages "chapter and pages" output.check new.block format.book.crossref output.nonnull } if$ format.edition output format.date "year" output.check format.isbn output format.doi output - format.eprint output + format.arxivid output format.url output new.block note output fin.entry } FUNCTION {incollection} { output.bibitem format.authors "author" output.check author format.key output new.block format.title "title" output.check new.block crossref missing$ { format.in.ed.booktitle "booktitle" output.check format.bvolume output format.number.series output format.chapter.pages output new.sentence publisher "publisher" output.check address output format.edition output format.date "year" output.check } { format.incoll.inproc.crossref output.nonnull format.chapter.pages output } if$ format.isbn output format.doi output - format.eprint output + format.arxivid output format.url output new.block note output fin.entry } FUNCTION {inproceedings} { output.bibitem format.authors "author" output.check author format.key output new.block format.title "title" output.check new.block crossref missing$ { format.in.ed.booktitle "booktitle" output.check format.bvolume output format.number.series output format.pages output address empty$ { organization publisher new.sentence.checkb organization output publisher output format.date "year" output.check } { address output.nonnull format.date "year" output.check new.sentence organization output publisher output } if$ } { format.incoll.inproc.crossref output.nonnull format.pages output } if$ format.isbn output format.doi output - format.eprint output + format.arxivid output format.url output new.block note output fin.entry } FUNCTION {conference} { inproceedings } FUNCTION {manual} { output.bibitem format.authors output author format.key output new.block format.btitle "title" output.check organization address new.block.checkb organization output address output format.edition output format.date output format.url output new.block note output fin.entry } FUNCTION {mastersthesis} { output.bibitem format.authors "author" output.check author format.key output new.block format.title "title" output.check new.block "Master's thesis" format.thesis.type output.nonnull school "school" output.check address output format.date "year" output.check format.url output new.block note output fin.entry } FUNCTION {misc} { output.bibitem format.authors output author format.key output title howpublished new.block.checkb format.title output howpublished new.block.checka howpublished output format.date output format.issn output format.url output new.block note output fin.entry empty.misc.check } FUNCTION {phdthesis} { output.bibitem format.authors "author" output.check author format.key output new.block format.btitle "title" output.check new.block "PhD thesis" format.thesis.type output.nonnull school "school" output.check address output format.date "year" output.check format.url output new.block note output fin.entry } FUNCTION {proceedings} { output.bibitem format.editors output editor format.key output new.block format.btitle "title" output.check format.bvolume output format.number.series output address output format.date "year" output.check new.sentence organization output publisher output format.isbn output format.doi output - format.eprint output + format.arxivid output format.url output new.block note output fin.entry } FUNCTION {techreport} { output.bibitem format.authors "author" output.check author format.key output new.block format.title "title" output.check new.block format.tr.number output.nonnull institution "institution" output.check address output format.date "year" output.check format.url output new.block note output fin.entry } FUNCTION {unpublished} { output.bibitem format.authors "author" output.check author format.key output new.block format.title "title" output.check new.block note "note" output.check format.date output format.url output fin.entry } FUNCTION {default.type} { misc } MACRO {jan} {"January"} MACRO {feb} {"February"} MACRO {mar} {"March"} MACRO {apr} {"April"} MACRO {may} {"May"} MACRO {jun} {"June"} MACRO {jul} {"July"} MACRO {aug} {"August"} MACRO {sep} {"September"} MACRO {oct} {"October"} MACRO {nov} {"November"} MACRO {dec} {"December"} MACRO {acmcs} {"ACM Computing Surveys"} MACRO {acta} {"Acta Informatica"} MACRO {cacm} {"Communications of the ACM"} MACRO {ibmjrd} {"IBM Journal of Research and Development"} MACRO {ibmsj} {"IBM Systems Journal"} MACRO {ieeese} {"IEEE Transactions on Software Engineering"} MACRO {ieeetc} {"IEEE Transactions on Computers"} MACRO {ieeetcad} {"IEEE Transactions on Computer-Aided Design of Integrated Circuits"} MACRO {ipl} {"Information Processing Letters"} MACRO {jacm} {"Journal of the ACM"} MACRO {jcss} {"Journal of Computer and System Sciences"} MACRO {scp} {"Science of Computer Programming"} MACRO {sicomp} {"SIAM Journal on Computing"} MACRO {tocs} {"ACM Transactions on Computer Systems"} MACRO {tods} {"ACM Transactions on Database Systems"} MACRO {tog} {"ACM Transactions on Graphics"} MACRO {toms} {"ACM Transactions on Mathematical Software"} MACRO {toois} {"ACM Transactions on Office Information Systems"} MACRO {toplas} {"ACM Transactions on Programming Languages and Systems"} MACRO {tcs} {"Theoretical Computer Science"} READ FUNCTION {sortify} { purify$ "l" change.case$ } INTEGERS { len } FUNCTION {chop.word} { 's := 'len := s #1 len substring$ = { s len #1 + global.max$ substring$ } 's if$ } FUNCTION {format.lab.names} { 's := s #1 "{vv~}{ll}" format.name$ s num.names$ duplicate$ #2 > { pop$ " et~al." * } { #2 < 'skip$ { s #2 "{ff }{vv }{ll}{ jj}" format.name$ "others" = { " et~al." * } { " and " * s #2 "{vv~}{ll}" format.name$ * } if$ } if$ } if$ } FUNCTION {author.key.label} { author empty$ { key empty$ { cite$ #1 #3 substring$ } 'key if$ } { author format.lab.names } if$ } FUNCTION {author.editor.key.label} { author empty$ { editor empty$ { key empty$ { cite$ #1 #3 substring$ } 'key if$ } { editor format.lab.names } if$ } { author format.lab.names } if$ } FUNCTION {author.key.organization.label} { author empty$ { key empty$ { organization empty$ { cite$ #1 #3 substring$ } { "The " #4 organization chop.word #3 text.prefix$ } if$ } 'key if$ } { author format.lab.names } if$ } FUNCTION {editor.key.organization.label} { editor empty$ { key empty$ { organization empty$ { cite$ #1 #3 substring$ } { "The " #4 organization chop.word #3 text.prefix$ } if$ } 'key if$ } { editor format.lab.names } if$ } FUNCTION {calc.short.authors} { type$ "book" = type$ "inbook" = or 'author.editor.key.label { type$ "proceedings" = 'editor.key.organization.label { type$ "manual" = 'author.key.organization.label 'author.key.label if$ } if$ } if$ 'short.list := } FUNCTION {calc.label} { calc.short.authors short.list "(" * year duplicate$ empty$ short.list key field.or.null = or { pop$ "" } 'skip$ if$ * 'label := } INTEGERS { seq.num } FUNCTION {init.seq} { #0 'seq.num :=} EXECUTE {init.seq} FUNCTION {int.to.fix} { "000000000" swap$ int.to.str$ * #-1 #10 substring$ } FUNCTION {presort} { calc.label label sortify " " * seq.num #1 + 'seq.num := seq.num int.to.fix 'sort.label := sort.label * #1 entry.max$ substring$ 'sort.key$ := } ITERATE {presort} SORT STRINGS { longest.label last.label next.extra } INTEGERS { longest.label.width last.extra.num number.label } FUNCTION {initialize.longest.label} { "" 'longest.label := #0 int.to.chr$ 'last.label := "" 'next.extra := #0 'longest.label.width := #0 'last.extra.num := #0 'number.label := } FUNCTION {forward.pass} { last.label label = { last.extra.num #1 + 'last.extra.num := last.extra.num int.to.chr$ 'extra.label := } { "a" chr.to.int$ 'last.extra.num := "" 'extra.label := label 'last.label := } if$ number.label #1 + 'number.label := } FUNCTION {reverse.pass} { next.extra "b" = { "a" 'extra.label := } 'skip$ if$ extra.label 'next.extra := extra.label duplicate$ empty$ 'skip$ { "{\natexlab{" swap$ * "}}" * } if$ 'extra.label := label extra.label * 'label := } EXECUTE {initialize.longest.label} ITERATE {forward.pass} REVERSE {reverse.pass} FUNCTION {bib.sort.order} { sort.label 'sort.key$ := } ITERATE {bib.sort.order} SORT FUNCTION {begin.bib} { preamble$ empty$ 'skip$ { preamble$ write$ newline$ } if$ "\begin{thebibliography}{" number.label int.to.str$ * "}" * write$ newline$ "\providecommand{\natexlab}[1]{#1}" write$ newline$ "\providecommand{\url}[1]{\texttt{#1}}" write$ newline$ "\expandafter\ifx\csname urlstyle\endcsname\relax" write$ newline$ - " \providecommand{\eprint}[1]{eprint: #1}\else" + " \providecommand{\arxivid}[1]{arxivid: #1}\else" write$ newline$ - " \providecommand{\eprint}{eprint: \begingroup \urlstyle{rm}\Url}\fi" + " \providecommand{\arxivid}{arxivid: \begingroup \urlstyle{rm}\Url}\fi" write$ newline$ "\expandafter\ifx\csname urlstyle\endcsname\relax" write$ newline$ " \providecommand{\doi}[1]{doi: #1}\else" write$ newline$ " \providecommand{\doi}{doi: \begingroup \urlstyle{rm}\Url}\fi" write$ newline$ } EXECUTE {begin.bib} EXECUTE {init.state.consts} ITERATE {call.type$} FUNCTION {end.bib} { newline$ "\end{thebibliography}" write$ newline$ } EXECUTE {end.bib}